react-hooks/exhaustive-deps
Require the dependency array of a hook to list every value its callback reads
Also called react_hooks/exhaustive-deps, react/exhaustive-deps
React runs an effect again, or recomputes a memoized value, only when something in the dependency array changes. If the callback reads a prop or a state value that isn't in the array, it keeps the value from an earlier render. The effect doesn't run again when the prop changes, and the screen shows old data.
The rule checks useEffect, useLayoutEffect, useMemo, useCallback, and useImperativeHandle. It also reports dependencies that do nothing, dependency lists it can't read, and an async function passed to an effect. You don't have to list values that never change between renders: a setState function, a dispatch from useReducer, a ref from useRef, and anything declared outside the component.
This rule is on by default as a warning if your package.json depends on react.
Most reports come with a suggestion that rewrites the array. --fix and --fix-suggestions don't apply it, because a new dependency changes when the effect runs. --fix-dangerously does.
Configure#
On by default as a warning. To turn it off:
[lint.rules]
"react-hooks/exhaustive-deps" = "off" # or "error"Every option, set to its default. The ones that are commented out are unset by default.
[lint.rules]
"react-hooks/exhaustive-deps" = ["warn", {
# additionalHooks = <string>,
requireExplicitEffectDeps = false,
enableDangerousAutofixThisMayCauseInfiniteLoops = false,
experimental_autoDependenciesHooks = [], # list of strings
}]Examples#
function Profile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
loadUser(userId).then(setUser);
}, []React Hook useEffect has a missing dependency: userId. Either include it or remove the dependency array.); // keeps showing the first user when userId changes
return <h1>{user?.name}</h1>;
}function Profile({ userId }) {
useEffect(async () => {Effect callbacks are synchronous to prevent race conditions. Put the async function inside:useEffect(() => {
async function fetchData() {
// You can await here
const response = await MyAPI.getData(someId);
// ...
}
fetchData();
}, [someId]); // Or [] if effect doesn't need props or stateLearn more about data fetching with Hooks: react.dev/link/hooks-data-fetching
await track(userId);
}, [userId]);
}function Profile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
loadUser(userId).then(setUser);
}, [userId]);
return <h1>{user?.name}</h1>;
}function Profile({ userId }) {
useEffect(() => {
async function send() {
await track(userId);
}
send();
}, [userId]);
}An effect without an array runs after every render, so nothing can be missing:
function Title({ text }) {
useEffect(() => {
document.title = text;
});
}Options#
| Option | Default | What it does |
|---|---|---|
additionalHooks | A regular expression that matches the names of your own hooks that take a callback and a dependency array. | |
requireExplicitEffectDeps | false | Report an effect that has no second argument. Write undefined to say "after every render" on purpose. |
enableDangerousAutofixThisMayCauseInfiniteLoops | false | Let --fix apply the suggestions. A new dependency can make an effect run in a loop, so read the result. |
experimental_autoDependenciesHooks | [] | Names of hooks that get their dependencies from a compiler when you pass null. |
{ "additionalHooks": "^useDebouncedEffect$" }1 problemfunction Search({ query }) {
useDebouncedEffect(() => {
search(query);
}, []React Hook useDebouncedEffect has a missing dependency: query. Either include it or remove the dependency array.);
}{ "requireExplicitEffectDeps": true }1 problemfunction Title({ text }) {
useEffectReact Hook useEffect always requires dependencies. Please add a dependency array or an explicit undefined(() => {
document.title = text;
});
}