useExhaustiveDependencies
Summary
Section titled “Summary”- Rule available since:
v1.0.0 - Diagnostic Category:
lint/correctness/useExhaustiveDependencies - This rule is recommended, meaning it is enabled by default.
- This rule has an unsafe fix.
- The default severity of this rule is error.
- This rule belongs to the following domains:
- Sources:
- Same as
react-hooks/exhaustive-deps
- Same as
How to configure
Section titled “How to configure”{ "linter": { "rules": { "correctness": { "useExhaustiveDependencies": "error" } } }}Description
Section titled “Description”Enforce correct dependency usage within React hooks.
React components have access to various hooks that can perform various actions like querying and updating state.
For hooks that trigger whenever a variable changes (such as useEffect and useMemo),
React relies on the hook’s listed dependencies array to determine when to re-compute Effects and re-render the page.
This can lead to unexpected behavior when dependencies are incorrectly specified:
function ticker() { const [count, setCount] = useState(0);
/** Increment the count once per second. */ function onTick() { setCount(count + 1); }
// React _thinks_ this code doesn't depend on anything else, so // it will only use the _initial_ version of `onTick` when rendering the component. // As a result, our normally-dynamic counter will always display 1! // This is referred to as a "stale closure", and is a common pitfall for beginners. useEffect(() => { const id = setInterval(onTick, 1000); return () => clearInterval(id); }, []);
return <h1>Counter: {count}</h1>;}function apples() { const [count, setCount] = useState(0); const [message, setMessage] = useState("We have 0 apples!");
// React _thinks_ this code depends on BOTH `count` and `message`, and will re-run the hook whenever // `message` is changed despite it not actually being used inside the closure. // In fact, this will create an infinite loop due to our hook updating `message` and triggering itself again! useEffect(() => { setMessage(`We have ${count} apples!`) }, [count, message]);
}This rule attempts to prevent such issues by diagnosing potentially incorrect or invalid usages of hook dependencies.
Default Behavior
Section titled “Default Behavior”By default, the following hooks (and their Preact counterparts) will have their arguments checked by this rule:
useEffectuseLayoutEffectuseInsertionEffectuseCallbackuseMemouseImperativeHandle
Stable results
Section titled “Stable results”When a hook is known to have a stable return value (one whose identity doesn’t change across invocations),
that value doesn’t need to and should not be specified as a dependency.
For example, setters returned by React’s useState hook will not change throughout the lifetime of a program
and should therefore be omitted.
By default, the following hooks are considered to have stable return values:
useState(index 1)useReducer(index 1)useTransition(index 1)useRefuseEffectEvent
If you want to add custom hooks to the rule’s diagnostics or specify your own functions with stable results, see the options section for more information.
Examples
Section titled “Examples”Invalid
Section titled “Invalid”import { useEffect } from "react";
function component() { let a = 1; useEffect(() => { console.log(a); }, []);}code-block.js:5:3 lint/correctness/useExhaustiveDependencies FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ This hook does not specify its dependency on a.
3 │ function component() {
4 │ let a = 1;
> 5 │ useEffect(() => {
│ ^^^^^^^^^
6 │ console.log(a);
7 │ }, []);
ℹ This dependency is being used here, but is not specified in the hook dependency list.
4 │ let a = 1;
5 │ useEffect(() => {
> 6 │ console.log(a);
│ ^
7 │ }, []);
8 │ }
ℹ React relies on hook dependencies to determine when to re-compute Effects.
Failing to specify dependencies can result in Effects not updating correctly when state changes.
These “stale closures” are a common source of surprising bugs.
ℹ Either include it or remove the dependency array.
ℹ Unsafe fix: Add the missing dependency to the list.
7 │ ··},·[a]);
│ +
import { useEffect } from "react";
function badComponent() { let a = 1; useEffect(() => { console.log(a); }, "not an array");}code-block.js:7:6 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ This dependencies list is not an array literal.
5 │ useEffect(() => {
6 │ console.log(a);
> 7 │ }, “not an array”);
│ ^^^^^^^^^^^^^^
8 │ }
9 │
ℹ Biome can’t statically verify whether you’ve passed the correct dependencies.
Replace the argument with an array literal and list your dependencies within it.
import { useEffect } from "react";
function component() { let unused = 1; useEffect(() => {}, [unused]);}code-block.js:5:5 lint/correctness/useExhaustiveDependencies FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ This hook specifies more dependencies than necessary: unused.
3 │ function component() {
4 │ let unused = 1;
> 5 │ useEffect(() => {}, [unused]);
│ ^^^^^^^^^
6 │ }
7 │
ℹ This dependency can be removed from the list.
3 │ function component() {
4 │ let unused = 1;
> 5 │ useEffect(() => {}, [unused]);
│ ^^^^^^
6 │ }
7 │
ℹ React relies on hook dependencies to determine when to re-compute Effects.
Specifying more dependencies than required can lead to unnecessary re-rendering
and degraded performance.
ℹ Unsafe fix: Remove the extra dependencies from the list.
5 │ ····useEffect(()·=>·{},·[unused]);
│ ------
import { useEffect, useState } from "react";
function component() { const [name, setName] = useState(); useEffect(() => { console.log(name); setName("i never change and don't need to be here"); }, [name, setName]);}code-block.js:5:3 lint/correctness/useExhaustiveDependencies FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ This hook specifies more dependencies than necessary: setName.
3 │ function component() {
4 │ const [name, setName] = useState();
> 5 │ useEffect(() => {
│ ^^^^^^^^^
6 │ console.log(name);
7 │ setName(“i never change and don’t need to be here”);
ℹ This dependency can be removed from the list.
6 │ console.log(name);
7 │ setName(“i never change and don’t need to be here”);
> 8 │ }, [name, setName]);
│ ^^^^^^^
9 │ }
10 │
ℹ React relies on hook dependencies to determine when to re-compute Effects.
Specifying more dependencies than required can lead to unnecessary re-rendering
and degraded performance.
ℹ Unsafe fix: Remove the extra dependencies from the list.
8 │ ··},·[name,·setName]);
│ ---------
import { useEffect, useState } from "react";
function component() { const name = "foo" // name doesn't change, so specifying it is redundant useEffect(() => { console.log(name); }, [name]);}code-block.js:6:3 lint/correctness/useExhaustiveDependencies FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ This hook specifies more dependencies than necessary: name.
4 │ const name = “foo”
5 │ // name doesn’t change, so specifying it is redundant
> 6 │ useEffect(() => {
│ ^^^^^^^^^
7 │ console.log(name);
8 │ }, [name]);
ℹ This dependency can be removed from the list.
6 │ useEffect(() => {
7 │ console.log(name);
> 8 │ }, [name]);
│ ^^^^
9 │ }
10 │
ℹ React relies on hook dependencies to determine when to re-compute Effects.
Specifying more dependencies than required can lead to unnecessary re-rendering
and degraded performance.
ℹ Unsafe fix: Remove the extra dependencies from the list.
8 │ ··},·[name]);
│ ----
import { useEffect } from "react";
function component() { let a = 1; const b = a + 1; useEffect(() => { console.log(b); }, []);}code-block.js:6:3 lint/correctness/useExhaustiveDependencies FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ This hook does not specify its dependency on b.
4 │ let a = 1;
5 │ const b = a + 1;
> 6 │ useEffect(() => {
│ ^^^^^^^^^
7 │ console.log(b);
8 │ }, []);
ℹ This dependency is being used here, but is not specified in the hook dependency list.
5 │ const b = a + 1;
6 │ useEffect(() => {
> 7 │ console.log(b);
│ ^
8 │ }, []);
9 │ }
ℹ React relies on hook dependencies to determine when to re-compute Effects.
Failing to specify dependencies can result in Effects not updating correctly when state changes.
These “stale closures” are a common source of surprising bugs.
ℹ Either include it or remove the dependency array.
ℹ Unsafe fix: Add the missing dependency to the list.
8 │ ··},·[b]);
│ +
import { useEffect } from "react";
function component() { let a = 1; useEffect(() => { console.log(a); }, [a]);}import { useEffect } from "react";
function component() { const SECONDS_PER_DAY = 60 * 60 * 24; useEffect(() => { console.log(SECONDS_PER_DAY); });}import { useEffect, useState } from "react";
function component() { const [name, setName] = useState(); useEffect(() => { console.log(name); setName(""); }, [name]);}Hooks not imported from React are ignored by default (unless specified inside rule options)
import type { EffectCallback, DependencyList } from "react";// custom useEffect functiondeclare function useEffect(cb: EffectCallback, deps?: DependencyList): void;
function component() { let name = "John Doe"; useEffect(() => { console.log(name); }, []);}Ignoring a specific dependency
Section titled “Ignoring a specific dependency”Sometimes you may wish to ignore a diagnostic about a specific dependency without disabling all linting for that hook. To do so, you may specify the name of a specific dependency between parentheses, like this:
import { useEffect } from "react";
function component() { let a = 1; // biome-ignore lint/correctness/useExhaustiveDependencies(a): suppress dependency a useEffect(() => { console.log(a); }, []);}If you wish to ignore multiple dependencies, you can add multiple comments and add a reason for each:
import { useEffect } from "react";
function component() { let a = 1; let b = 1; // biome-ignore lint/correctness/useExhaustiveDependencies(a): suppress dependency a // biome-ignore lint/correctness/useExhaustiveDependencies(b): suppress dependency b useEffect(() => { console.log(a, b); }, []);}Options
Section titled “Options”Allows specifying custom hooks (from libraries or internal projects) whose dependencies should be checked and/or which are known to have stable return values.
For every hook whose dependencies you want validated, you must specify the index of both the closure using the dependencies and the dependencies array to validate it against.
Example
Section titled “Example”{ "linter": { "rules": { "correctness": { "useExhaustiveDependencies": { "options": { "hooks": [ { "name": "useLocation", "closureIndex": 0, "dependenciesIndex": 1 }, { "name": "useQuery", "closureIndex": 2, "dependenciesIndex": 0 } ] } } } } }}This would enable checks on the following code snippets:
function Foo() { let stateVar = 1; useLocation(() => {console.log(stateVar)}, []);}code-block.js:3:3 lint/correctness/useExhaustiveDependencies FIXABLE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ This hook does not specify its dependency on stateVar.
1 │ function Foo() {
2 │ let stateVar = 1;
> 3 │ useLocation(() => {console.log(stateVar)}, []);
│ ^^^^^^^^^^^
4 │ }
5 │
ℹ This dependency is being used here, but is not specified in the hook dependency list.
1 │ function Foo() {
2 │ let stateVar = 1;
> 3 │ useLocation(() => {console.log(stateVar)}, []);
│ ^^^^^^^^
4 │ }
5 │
ℹ React relies on hook dependencies to determine when to re-compute Effects.
Failing to specify dependencies can result in Effects not updating correctly when state changes.
These “stale closures” are a common source of surprising bugs.
ℹ Either include it or remove the dependency array.
ℹ Unsafe fix: Add the missing dependency to the list.
3 │ ··useLocation(()·=>·{console.log(stateVar)},·[stateVar]);
│ ++++++++
function Foo() { let stateVar = 1; useQuery([stateVar], "smthng", () => {console.log(stateVar)});}Configuring stable results
Section titled “Configuring stable results”As previously discussed, the lint rule takes into account so-called stable results and will ensure any such variables are not specified as dependencies.
You can specify custom functions as returning stable results in one of four ways:
"stableResult": true— marks the return value as stable. An example of a React hook that would be configured like this isuseRef()."stableResult": [1]— expects the return value to be an array and marks the given indices as stable. An example of a React hook that would be configured like this isuseState()."stableResult": 1— shorthand for option 2 ("stableResult": [1]). Useful for hooks that only have a single stable return."stableResult": ["setValue"]— expects the return value to be an object and marks the properties with the given keys as stable.
Example
Section titled “Example”{ "linter": { "rules": { "correctness": { "useExhaustiveDependencies": { "options": { "hooks": [ { "name": "useDispatch", "stableResult": true } ] } } } } }}With this configuration, the following is valid:
const dispatch = useDispatch();// No need to list `dispatch` as dependency since it doesn't changeconst doAction = useCallback(() => dispatch(someAction()), []);reportUnnecessaryDependencies
Section titled “reportUnnecessaryDependencies”If set to false, the rule will not trigger diagnostics for unused dependencies passed to hooks that do not use them.
Default: true
Example
Section titled “Example”{ "linter": { "rules": { "correctness": { "useExhaustiveDependencies": { "options": { "reportUnnecessaryDependencies": false } } } } }}import { useEffect } from "react";
function Foo() { let stateVar = 1; // not used but still OK useEffect(() => {}, [stateVar]);}reportMissingDependenciesArray
Section titled “reportMissingDependenciesArray”If enabled, the rule will also trigger diagnostics for hooks that lack dependency arrays altogether, requiring any hooks lacking dependencies to explicitly specify an empty array.
Default: false
Example
Section titled “Example”{ "linter": { "rules": { "correctness": { "useExhaustiveDependencies": { "options": { "reportMissingDependenciesArray": true } } } } }}function noArrayYesProblem() { let stateVar = 1; React.useEffect(() => {});}code-block.jsx:3:9 lint/correctness/useExhaustiveDependencies ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✖ This hook does not have a dependencies array.
1 │ function noArrayYesProblem() {
2 │ let stateVar = 1;
> 3 │ React.useEffect(() => {});
│ ^^^^^^^^^
4 │ }
5 │
ℹ React relies on hook dependencies to determine when to re-compute Effects.
Add an explicit array (i.e. []) and list the callback’s dependencies inside it.
Related links
Section titled “Related links”Copyright (c) 2023-present Biome Developers and Contributors.