exhaustive-deps
Verifies the list of dependencies for Hooks like 'useEffect' and similar.
This rule is currently in rc and only available in v3.0.0 rc releases.
Full Name in eslint-plugin-react-x@rc
react-x/exhaustive-depsFull Name in @eslint-react/eslint-plugin@rc
@eslint-react/exhaustive-depsFeatures
⚙️ 🔧
Presets
x
recommended
recommended-typescript
recommended-type-checked
strict
strict-typescript
strict-type-checked
Rule Details
React hooks like useEffect, useMemo, and useCallback accept dependency arrays. When a value referenced inside these hooks is not 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
If you find yourself fighting with this rule, you likely need to restructure your code rather than suppress the warning. See Removing Effect Dependencies to learn how.
Invalid
import { useEffect } from "react";
function MyComponent({ userId }) {
useEffect(() => {
fetchUser(userId);
// ^^^ 'userId' is missing from the dependency array.
}, []); // Missing 'userId'
}import { useMemo } from "react";
function MyComponent({ items, sortOrder }) {
const sorted = useMemo(() => {
return items.sort(sortOrder);
// ^^^ 'sortOrder' is missing from the dependency array.
}, [items]); // Missing 'sortOrder'
}import { useCallback } from "react";
function MyComponent({ onSave, value }) {
const handleClick = useCallback(() => {
onSave(value);
// ^^^ 'value' is missing from the dependency array.
}, [onSave]); // Missing 'value'
}Valid
import { useEffect } from "react";
function MyComponent({ userId }) {
useEffect(() => {
fetchUser(userId);
}, [userId]);
}import { useMemo } from "react";
function MyComponent({ items, sortOrder }) {
const sorted = useMemo(() => {
return items.sort(sortOrder);
}, [items, sortOrder]);
}import { useCallback } from "react";
function MyComponent({ onSave, value }) {
const handleClick = useCallback(() => {
onSave(value);
}, [onSave, value]);
}Troubleshooting
Adding a function dependency causes infinite loops
When a function is defined in the component body, it is recreated on every render, causing the effect to re-run endlessly:
// ❌ Causes infinite loop — logItems is a new reference on every render
function MyComponent({ items }) {
const logItems = () => {
console.log(items);
};
useEffect(() => {
logItems();
}, [logItems]);
}In most cases, move the function call out of the effect or inline the logic directly:
// ✅ Inline the logic inside the effect
function MyComponent({ items }) {
useEffect(() => {
console.log(items);
}, [items]);
}If the function is genuinely needed as a stable reference, wrap it with useCallback:
// ✅ useCallback keeps the function reference stable
function MyComponent({ items }) {
const logItems = useCallback(() => {
console.log(items);
}, [items]);
useEffect(() => {
logItems();
}, [logItems]);
}Running an effect only once on mount
You want to run an effect once on mount, but the linter complains about missing dependencies:
// ❌ Missing dependency
function MyComponent({ userId }) {
useEffect(() => {
sendAnalytics(userId);
}, []); // Missing 'userId'
}The recommended fix is to include the dependency. If userId changes and you want the effect to re-run, this is the correct behavior:
// ✅ Include the dependency
function MyComponent({ userId }) {
useEffect(() => {
sendAnalytics(userId);
}, [userId]);
}If you truly need to run once and read the latest value without reacting to changes, use a ref:
// ✅ Use a ref guard if you only want to run once
function MyComponent({ userId }) {
const sent = useRef(false);
useEffect(() => {
if (sent.current) return;
sent.current = true;
sendAnalytics(userId);
}, [userId]);
}Options
Rule Options
This rule accepts an optional configuration object:
type Options = {
additionalHooks?: string;
enableDangerousAutofixThisMayCauseInfiniteLoops?: boolean;
};{
"react-x/exhaustive-deps": ["warn", {
"additionalHooks": "(useMyCustomHook|useAnotherHook)"
}]
}additionalHooks
A regex string matching custom hooks that should be checked for exhaustive dependencies in the same way as useEffect, useMemo, and useCallback.
Note: When this rule-level option is specified, it takes precedence over the shared settings configuration described below.
enableDangerousAutofixThisMayCauseInfiniteLoops
When true, promotes the suggested fix to an auto-fix. This is dangerous — automatically adding all missing dependencies can introduce infinite loops in effects that intentionally omit dependencies. Use only as a one-time migration aid.
Default: false
Shared Settings
Custom effect hooks can also be configured via shared ESLint settings, which apply consistently across all rules in the plugin:
{
"settings": {
"react-x": {
"additionalEffectHooks": "(useMyEffect|useCustomEffect)"
}
}
}Resources
Further Reading
- React Docs: Removing Effect Dependencies
- React Docs:
useEffect - React Docs:
useCallback - React Docs:
useMemo - React Docs:
exhaustive-depsLint Rule
See Also
react-x/rules-of-hooks
Enforces the Rules of Hooks.