useEffect Dependency Arrays
useEffect Dependency Arrays
Section titled “useEffect Dependency Arrays”Introduction
Section titled “Introduction”The dependency array tells React when to re-run an effect. It’s the second argument to useEffect. Understanding how it works is essential for writing correct, predictable effects.
How Dependency Comparison Works
Section titled “How Dependency Comparison Works”React compares dependencies using Object.is — the same comparison used by React.memo and useMemo. For primitive values (number, string, boolean), it compares by value. For objects, arrays, and functions, it compares by reference.
Object.is(5, 5) // true — primitives compare by valueObject.is([1, 2], [1, 2]) // false — new array, different referenceObject.is({a: 1}, {a: 1}) // false — new object, different referenceThis means if you pass an inline object or array as a dependency, the effect will re-run on every render.
Dependency Patterns
Section titled “Dependency Patterns”Empty Array [] — Run Once
Section titled “Empty Array [] — Run Once”useEffect(() => { fetchUserProfile();}, []); // Effect runs only on mountThe effect runs after the first render and never again. Cleanup runs on unmount.
No Array — Run After Every Render
Section titled “No Array — Run After Every Render”useEffect(() => { document.title = `Count: ${count}`;}); // No array — runs after every render⚠ Risk: Without a dependency array, the effect runs after every render, which can cause infinite loops if the effect sets state.
With Dependencies [dep1, dep2] — Run When Deps Change
Section titled “With Dependencies [dep1, dep2] — Run When Deps Change”useEffect(() => { fetch(`/api/users/${userId}`);}, [userId]); // Re-runs when userId changesThe effect runs on mount and whenever userId changes. If userId is the same across renders, the effect is skipped.
Rules for Dependencies
Section titled “Rules for Dependencies”- Include every reactive value used inside the effect: props, state, and derived values.
- Do not include
setStatefunctions,useRefrefs, or stable functions from custom hooks (these have stable references). - Do not include the effect’s own internal variables.
Common Dependency Mistakes
Section titled “Common Dependency Mistakes”Missing Dependencies
Section titled “Missing Dependencies”const [count, setCount] = useState(0);
// ❌ BAD — count is used inside but missing from depsuseEffect(() => { document.title = `Count: ${count}`;}, []); // Stale closure — always shows count = 0
// ✅ GOOD — count is includeduseEffect(() => { document.title = `Count: ${count}`;}, [count]);Unnecessary Dependencies
Section titled “Unnecessary Dependencies”// ❌ BAD — setCount is stable, doesn't need to be in depsuseEffect(() => { setCount(count + 1);}, [count, setCount]);
// ✅ GOOD — setCount is guaranteed stable by ReactuseEffect(() => { setCount(count + 1);}, [count]);Objects and Arrays as Dependencies
Section titled “Objects and Arrays as Dependencies”// ❌ BAD — options object is recreated every renderfunction Search({ options }) { useEffect(() => { fetchResults(options); }, [options]); // Runs on every render! options is always a new object}
// ✅ GOOD — use specific primitive valuesfunction Search({ options }) { useEffect(() => { fetchResults(options); }, [options.query, options.page]); // Only runs when query or page changes}Using the ESLint Plugin
Section titled “Using the ESLint Plugin”The eslint-plugin-react-hooks package includes the exhaustive-deps rule, which automatically warns about missing or incorrect dependencies:
npm install eslint-plugin-react-hooks --save-dev{ "plugins": ["react-hooks"], "rules": { "react-hooks/exhaustive-deps": "warn" }}If the lint rule suggests adding a dependency, add it. If you genuinely can’t (e.g., the value causes an infinite loop), add a comment to suppress the warning:
useEffect(() => { setCount(prev => prev + 1); // eslint-disable-next-line react-hooks/exhaustive-deps}, []); // Intentionally empty — we only want to run onceSummary
Section titled “Summary”- Deps are compared with
Object.is— primitives by value, objects by reference. []= run once on mount.[dep]= run when dep changes. No array = run every render.- Include every reactive value from the closure in the dependency array.
- Use the ESLint
exhaustive-depsrule to catch mistakes automatically. - Prefer primitive values over objects/arrays as dependencies.