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
The rule documentation & diagnostic messages for `useExhaustiveDependencies` have been reworked for improved clarity among those not immediately familiar with React idioms.
6
+
7
+
Among other things, it clearly explains why incorrect dependencies are undesireable.
Copy file name to clipboardExpand all lines: crates/biome_js_analyze/src/lint/correctness/use_exhaustive_dependencies.rs
+73-27Lines changed: 73 additions & 27 deletions
Original file line number
Diff line number
Diff line change
@@ -31,11 +31,10 @@ 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
-
/// For hooks that trigger whenever a variable changes ( notably `useEffect`, `useMemo`, and `useCallback`),
35
-
/// React attempts
34
+
/// For hooks that trigger whenever a variable changes (such as `useEffect`and `useMemo`),
35
+
/// React relies on the hook's listed dependencies array to determine when to re-compute Effects and re-render the page.
36
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:
37
+
/// This can lead to unexpected behavior when dependencies are incorrectly specified:
39
38
/// ```jsx,ignore
40
39
///
41
40
/// function ticker() {
@@ -47,8 +46,9 @@ declare_lint_rule! {
47
46
/// }
48
47
///
49
48
/// // 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.
49
+
/// // it will only use the _initial_ version of `onTick` when rendering the component.
50
+
/// // As a result, our normally-dynamic counter will always display 1!
51
+
/// // This is referred to as a "stale closure", and is a common pitfall for beginners.
52
52
/// useEffect(() => {
53
53
/// const id = setInterval(onTick, 1000);
54
54
/// return () => clearInterval(id);
@@ -58,9 +58,25 @@ declare_lint_rule! {
58
58
/// }
59
59
/// ```
60
60
///
61
+
/// ```jsx,ignore
62
+
/// function apples() {
63
+
/// const [count, setCount] = useState(0);
64
+
/// const [message, setMessage] = useState("We have 0 apples!");
65
+
///
66
+
/// // React _thinks_ this code depends on BOTH `count` and `message`, and will re-run the hook whenever
67
+
/// // `message` is changed despite it not actually being used inside the closure.
68
+
/// // In fact, this will create an infinite loop due to our hook updating `message` and triggering itself again!
69
+
/// useEffect(() => {
70
+
/// setMessage(`We have ${count} apples!`)
71
+
/// }, [count, message]);
72
+
///
73
+
/// }
74
+
/// ```
75
+
///
61
76
/// This rule attempts to prevent such issues by diagnosing potentially incorrect or invalid usages of hook dependencies.
62
77
///
63
-
/// By default, the following hooks (and their Preact counterparts) are checked by this rule:
78
+
/// ### Default Behavior
79
+
/// By default, the following hooks (and their Preact counterparts) will have their arguments checked by this rule:
64
80
///
65
81
/// - `useEffect`
66
82
/// - `useLayoutEffect`
@@ -69,14 +85,21 @@ declare_lint_rule! {
69
85
/// - `useMemo`
70
86
/// - `useImperativeHandle`
71
87
///
72
-
/// If you want to add more hooks to the rule's diagnostics, see the [options](#options) section for information.
73
-
///
74
-
/// ### Stable results
88
+
/// #### Stable results
75
89
/// 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.
90
+
/// that value doesn't need to and _should not_ be specified as a dependency.
91
+
/// For example, setters returned by React's `useState` hook will not change throughout the lifetime of a program
92
+
/// and should therefore be omitted.
79
93
///
94
+
/// By default, the following hooks are considered to have stable return values:
95
+
/// - `useState` (index 1)
96
+
/// - `useRef` (index 1)
97
+
/// - `useReducer` (index 1)
98
+
/// - `useTransition`
99
+
/// - `useEffectEvent`
100
+
///
101
+
/// If you want to add custom hooks to the rule's diagnostics or specify your own functions with stable results,
102
+
/// see the [options](#options) section for more information.
80
103
///
81
104
/// ## Examples
82
105
///
@@ -120,10 +143,22 @@ declare_lint_rule! {
120
143
/// const [name, setName] = useState();
121
144
/// useEffect(() => {
122
145
/// console.log(name);
123
-
/// setName("i am a beacon of stability");
146
+
/// setName("i never change and don't need to be here");
124
147
/// }, [name, setName]);
125
148
/// }
126
149
/// ```
150
+
///
151
+
/// ```js,expect_diagnostic
152
+
/// import { useEffect, useState } from "react";
153
+
///
154
+
/// function component() {
155
+
/// const name = "foo"
156
+
/// // name doesn't change, so specifying it is redundant`
157
+
/// useEffect(() => {
158
+
/// console.log(name);
159
+
/// }, [name]);
160
+
/// }
161
+
/// ```
127
162
///
128
163
/// ```js,expect_diagnostic
129
164
/// import { useEffect } from "react";
@@ -149,8 +184,6 @@ declare_lint_rule! {
149
184
/// }, [a]);
150
185
/// }
151
186
/// ```
152
-
///
153
-
/// Constants and recognized stable results do not (and should not) be specified as dependencies:
154
187
///
155
188
/// ```js
156
189
/// import { useEffect } from "react";
@@ -211,7 +244,11 @@ declare_lint_rule! {
211
244
/// }
212
245
/// ```
213
246
///
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.
247
+
/// :::caution
248
+
/// Mismatching code & dependencies has a **very high risk** of creating bugs in your components.
249
+
/// By suppressing the linter, you “lie” to React about the values your Effect depends on,
250
+
/// so prefer changing the code over suppressing the rule where possible.
251
+
/// :::
215
252
///
216
253
/// ## Options
217
254
///
@@ -224,7 +261,7 @@ declare_lint_rule! {
224
261
///
225
262
/// ##### Example
226
263
///
227
-
/// ```json,options
264
+
/// ```json,options
228
265
/// {
229
266
/// "options": {
230
267
/// "hooks": [
@@ -235,7 +272,7 @@ declare_lint_rule! {
235
272
/// }
236
273
/// ```
237
274
///
238
-
/// This would enable diagnostics on the aforementioned hooks, as follows:
275
+
/// This would enable diagnostics on the following code snippet:
239
276
///
240
277
/// ```js,options,expect_diagnostic
241
278
/// function Foo() {
@@ -250,7 +287,7 @@ declare_lint_rule! {
250
287
/// As previously discussed, the lint rule takes into account so-called ["stable results"](#stable-results)
251
288
/// and will ensure any such variables are _not_ specified as dependencies.
252
289
///
253
-
/// You can configure custom hooks that return stable results in one of four ways:
290
+
/// You can specify custom hooks that return stable results in one of four ways:
254
291
///
255
292
/// 1. `"stableResult": true` -- marks the return value as stable. An example
256
293
/// of a React hook that would be configured like this is `useRef()`.
@@ -283,7 +320,8 @@ declare_lint_rule! {
283
320
///
284
321
/// ### `reportUnnecessaryDependencies`
285
322
///
286
-
/// If enabled, the rule will also trigger diagnostics for unused dependencies passed to hooks that do not use them.
323
+
/// If disabled, the rule will not trigger diagnostics for unused dependencies passed to hooks that do not use them. \
324
+
/// Note that this can reduce performance and potentially cause infinite loops, so caution is advised.
287
325
///
288
326
/// Default: `true`
289
327
///
@@ -308,7 +346,7 @@ declare_lint_rule! {
308
346
/// ### `reportMissingDependenciesArray`
309
347
///
310
348
/// If enabled, the rule will also trigger diagnostics for hooks that lack dependency arrays altogether,
311
-
/// requiring any hooks lacking dependencies to explicitly state as such.
349
+
/// requiring any hooks lacking dependencies to explicitly specify an empty array.
312
350
///
313
351
/// Default: `false`
314
352
///
@@ -323,7 +361,7 @@ declare_lint_rule! {
323
361
/// ```
324
362
///
325
363
/// ```jsx,use_options,expect_diagnostic
326
-
/// function NoArrayYesProblem() {
364
+
/// function noArrayYesProblem() {
327
365
/// let stateVar = 1;
328
366
/// useEffect(() => {});
329
367
/// }
@@ -968,8 +1006,11 @@ impl Rule for UseExhaustiveDependencies {
968
1006
function_name_range,
969
1007
markup!{
970
1008
"This hook "<Emphasis>"does not specify"</Emphasis>" its dependency on "<Emphasis>{capture_text.as_ref()}</Emphasis>"."
971
-
"\nReact is unable to capture changes to variables not included inside hook dependencies, resulting in"<Emphasis>"outdated references"</Emphasis>"
972
-
"\nthat can produce unexpected results."
1009
+
},
1010
+
).note(markup!{
1011
+
"\nReact relies on hook dependencies to determine when to re-compute Effects."
1012
+
"\nFailing to specify dependencies can result in Effects "<Emphasis>"not updating correctly"</Emphasis>" when state changes."
1013
+
"\nThese \"stale closures\" are a common source of surprising bugs."
973
1014
},
974
1015
);
975
1016
@@ -1001,7 +1042,13 @@ impl Rule for UseExhaustiveDependencies {
1001
1042
rule_category!(),
1002
1043
function_name_range,
1003
1044
markup!{
1004
-
"This hook specifies more dependencies than necessary: "{deps_joined_with_comma}""
1045
+
"This hook specifies "<Emphasis>"more dependencies than necessary"</Emphasis>": "{deps_joined_with_comma}"."
1046
+
},
1047
+
)
1048
+
.note(markup!{
1049
+
"\nReact relies on hook dependencies to determine when to re-compute Effects."
1050
+
"\nSpecifying more dependencies than required can lead to "<Emphasis>"unnecessary re-rendering"</Emphasis>
1051
+
" and "<Emphasis>"degraded performance"</Emphasis>"."
1005
1052
},
1006
1053
);
1007
1054
@@ -1011,7 +1058,6 @@ impl Rule for UseExhaustiveDependencies {
1011
1058
diag = diag.detail(
1012
1059
dep.syntax().text_trimmed_range(),
1013
1060
"Outer scope values aren't valid dependencies because mutating them doesn't re-render the component.",
0 commit comments