You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: crates/biome_js_analyze/src/lint/correctness/use_exhaustive_dependencies.rs
+50-39Lines changed: 50 additions & 39 deletions
Original file line number
Diff line number
Diff line change
@@ -31,30 +31,53 @@ declare_lint_rule! {
31
31
/// React components have access to various [hooks](https://react.dev/reference/react/hooks) that can perform
32
32
/// various actions like querying and updating state.
33
33
///
34
-
/// Notably, React is **unable** to capture changes to variables not included inside a hook's dependency array, resulting in
35
-
/// any such references being "locked" to their initial values even if updated later.
36
-
/// Such "stale closures" are a common pitfall among React developers.
34
+
/// For hooks that trigger whenever a variable changes ( notably `useEffect`, `useMemo`, and `useCallback`),
35
+
/// React attempts
37
36
///
37
+
/// However, React is **unable** to capture changes to variables not included inside a hook's dependency array,
38
+
/// which can result in surprising behavior:
39
+
/// ```jsx,ignore
40
+
///
41
+
/// function ticker() {
42
+
/// const [count, setCount] = useState(0);
43
+
///
44
+
/// /** Increment the count once per second. */
45
+
/// function onTick() {
46
+
/// setCount(count + 1);
47
+
/// }
48
+
///
49
+
/// // React _thinks_ this code doesn't depend on anything else, so
50
+
/// // it will only use the initial version of `onTick` when rendering the component,
51
+
/// // making our normally-dynamic counter always display 1.
52
+
/// useEffect(() => {
53
+
/// const id = setInterval(onTick, 1000);
54
+
/// return () => clearInterval(id);
55
+
/// }, []);
56
+
///
57
+
/// return <h1>Counter: {count}</h1>;
58
+
/// }
59
+
/// ```
60
+
///
38
61
/// This rule attempts to prevent such issues by diagnosing potentially incorrect or invalid usages of hook dependencies.
39
62
///
40
-
/// By default, the rule will inspect the following built-in React hooks (as well as their Preact counterparts):
63
+
/// By default, the following hooks (and their Preact counterparts) are checked by this rule:
41
64
///
42
65
/// - `useEffect`
43
66
/// - `useLayoutEffect`
44
67
/// - `useInsertionEffect`
45
68
/// - `useCallback`
46
69
/// - `useMemo`
47
70
/// - `useImperativeHandle`
48
-
/// - `useState`
49
-
/// - `useReducer`
50
-
/// - `useRef`
51
-
/// - `useDebugValue`
52
-
/// - `useDeferredValue`
53
-
/// - `useTransition`
54
-
/// - `useEffectEvent`
55
71
///
56
-
/// If you want to add more hooks to the rule's diagnostics, see the [options](#options) section for more.
72
+
/// If you want to add more hooks to the rule's diagnostics, see the [options](#options) section for information.
73
+
///
74
+
/// ### Stable results
75
+
/// When a hook is known to have a stable return value (one whose identity doesn't change across invocations),
76
+
/// that value doesn't need to and should not be specified as a dependency.
77
+
/// For example, setters returned by React's `useState` hook will not change throughout the lifetime of a program,
78
+
/// and should be omitted as such - they will never change tho.
57
79
///
80
+
///
58
81
/// ## Examples
59
82
///
60
83
/// ### Invalid
@@ -81,14 +104,12 @@ declare_lint_rule! {
81
104
/// }
82
105
/// ```
83
106
///
84
-
///
85
107
/// ```js,expect_diagnostic
86
108
/// import { useEffect } from "react";
87
109
///
88
110
/// function component() {
89
111
/// let unused = 1;
90
-
/// useEffect(() => {
91
-
/// }, [unused]);
112
+
/// useEffect(() => {}, [unused]);
92
113
/// }
93
114
/// ```
94
115
///
@@ -99,7 +120,7 @@ declare_lint_rule! {
99
120
/// const [name, setName] = useState();
100
121
/// useEffect(() => {
101
122
/// console.log(name);
102
-
/// setName("shouldn't need to be here");
123
+
/// setName("i am a beacon of stability");
103
124
/// }, [name, setName]);
104
125
/// }
105
126
/// ```
@@ -129,14 +150,15 @@ declare_lint_rule! {
129
150
/// }
130
151
/// ```
131
152
///
153
+
/// Constants and recognized stable results do not (and should not) be specified as dependencies:
154
+
///
132
155
/// ```js
133
156
/// import { useEffect } from "react";
134
157
///
135
158
/// function component() {
136
-
/// const a = 1;
159
+
/// const SECONDS_PER_DAY = 60 * 60 * 24;
137
160
/// useEffect(() => {
138
-
/// // a is const here, so there is no mutable state to change
139
-
/// console.log(a);
161
+
/// console.log(SECONDS_PER_DAY);
140
162
/// });
141
163
/// }
142
164
/// ```
@@ -153,16 +175,6 @@ declare_lint_rule! {
153
175
/// }
154
176
/// ```
155
177
///
156
-
/// ```js
157
-
/// import { useEffect } from "react";
158
-
/// let outer = false;
159
-
/// function component() {
160
-
/// useEffect(() => {
161
-
/// outer = true;
162
-
/// }, []);
163
-
/// }
164
-
/// ```
165
-
///
166
178
/// ## Ignoring a specific dependency
167
179
///
168
180
/// Sometimes you may wish to ignore a diagnostic about a specific
@@ -199,6 +211,8 @@ declare_lint_rule! {
199
211
/// }
200
212
/// ```
201
213
///
214
+
/// > When dependencies don’t match the code, there is a **very high risk** of introducing bugs. By suppressing the linter, you “lie” to React about the values your Effect depends on.
0 commit comments