Validates that dependency arrays for React hooks contain all necessary dependencies.
Rule Details
Section titled “Rule Details”React hooks like useEffect, useMemo, and useCallback accept dependency arrays. When a value referenced inside these hooks isn’t included in the dependency array, React won’t re-run the effect or recalculate the value when that dependency changes. This causes stale closures where the hook uses outdated values.
Common Violations
Section titled “Common Violations”This error often happens when you try to “trick” React about dependencies to control when an effect runs. Effects should synchronize your component with external systems. The dependency array tells React which values the effect uses, so React knows when to re-synchronize.
If you find yourself fighting with the linter, you likely need to restructure your code. See Removing Effect Dependencies to learn how.
Invalid
Section titled “Invalid”Examples of incorrect code for this rule:
// ❌ Missing dependencyuseEffect(() => { console.log(count);}, []); // Missing 'count'
// ❌ Missing propuseEffect(() => { fetchUser(userId);}, []); // Missing 'userId'
// ❌ Incomplete dependenciesuseMemo(() => { return items.sort(sortOrder);}, [items]); // Missing 'sortOrder'Examples of correct code for this rule:
// ✅ All dependencies includeduseEffect(() => { console.log(count);}, [count]);
// ✅ All dependencies includeduseEffect(() => { fetchUser(userId);}, [userId]);Troubleshooting
Section titled “Troubleshooting”Adding a function dependency causes infinite loops
Section titled “Adding a function dependency causes infinite loops”You have an effect, but you’re creating a new function on every render:
// ❌ Causes infinite loopconst logItems = () => { console.log(items);};
useEffect(() => { logItems();}, [logItems]); // Infinite loop!In most cases, you don’t need the effect. Call the function where the action happens instead:
// ✅ Call it from the event handlerconst logItems = () => { console.log(items);};
return <button onClick={logItems}>Log</button>;
// ✅ Or derive during render if there's no side effectitems.forEach(item => { console.log(item);});If you genuinely need the effect (for example, to subscribe to something external), make the dependency stable:
// ✅ useCallback keeps the function reference stableconst logItems = useCallback(() => { console.log(items);}, [items]);
useEffect(() => { logItems();}, [logItems]);
// ✅ Or move the logic straight into the effectuseEffect(() => { console.log(items);}, [items]);Running an effect only once
Section titled “Running an effect only once”You want to run an effect once on mount, but the linter complains about missing dependencies:
// ❌ Missing dependencyuseEffect(() => { sendAnalytics(userId);}, []); // Missing 'userId'Either include the dependency (recommended) or use a ref if you truly need to run once:
// ✅ Include dependencyuseEffect(() => { sendAnalytics(userId);}, [userId]);
// ✅ Or use a ref guard inside an effectconst sent = useRef(false);
useEffect(() => { if (sent.current) { return; }
sent.current = true; sendAnalytics(userId);}, [userId]);Options
Section titled “Options”You can configure custom effect hooks using shared ESLint settings (available in eslint-plugin-react-hooks 6.1.1 and later):
}}additionalEffectHooks: Regex pattern matching custom hooks that should be checked for exhaustive dependencies. This configuration is shared across allreact-hooksrules.
For backward compatibility, this rule also accepts a rule-level option:
}}additionalHooks: Regex for hooks that should be checked for exhaustive dependencies. Note: If this rule-level option is specified, it takes precedence over the sharedsettingsconfiguration.