Skip to content

Commit 51702ce

Browse files
committed
typofix
1 parent ace5891 commit 51702ce

File tree

2 files changed

+80
-27
lines changed

2 files changed

+80
-27
lines changed

.changeset/old-seas-drum.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@biomejs/biome": patch
3+
---
4+
5+
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.

crates/biome_js_analyze/src/lint/correctness/use_exhaustive_dependencies.rs

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,10 @@ declare_lint_rule! {
3131
/// React components have access to various [hooks](https://react.dev/reference/react/hooks) that can perform
3232
/// various actions like querying and updating state.
3333
///
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.
3636
///
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:
3938
/// ```jsx,ignore
4039
///
4140
/// function ticker() {
@@ -47,8 +46,9 @@ declare_lint_rule! {
4746
/// }
4847
///
4948
/// // 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.
5252
/// useEffect(() => {
5353
/// const id = setInterval(onTick, 1000);
5454
/// return () => clearInterval(id);
@@ -58,9 +58,25 @@ declare_lint_rule! {
5858
/// }
5959
/// ```
6060
///
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+
///
6176
/// This rule attempts to prevent such issues by diagnosing potentially incorrect or invalid usages of hook dependencies.
6277
///
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:
6480
///
6581
/// - `useEffect`
6682
/// - `useLayoutEffect`
@@ -69,14 +85,21 @@ declare_lint_rule! {
6985
/// - `useMemo`
7086
/// - `useImperativeHandle`
7187
///
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
7589
/// 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.
7993
///
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.
80103
///
81104
/// ## Examples
82105
///
@@ -120,10 +143,22 @@ declare_lint_rule! {
120143
/// const [name, setName] = useState();
121144
/// useEffect(() => {
122145
/// console.log(name);
123-
/// setName("i am a beacon of stability");
146+
/// setName("i never change and don't need to be here");
124147
/// }, [name, setName]);
125148
/// }
126149
/// ```
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+
/// ```
127162
///
128163
/// ```js,expect_diagnostic
129164
/// import { useEffect } from "react";
@@ -149,8 +184,6 @@ declare_lint_rule! {
149184
/// }, [a]);
150185
/// }
151186
/// ```
152-
///
153-
/// Constants and recognized stable results do not (and should not) be specified as dependencies:
154187
///
155188
/// ```js
156189
/// import { useEffect } from "react";
@@ -211,7 +244,11 @@ declare_lint_rule! {
211244
/// }
212245
/// ```
213246
///
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+
/// :::
215252
///
216253
/// ## Options
217254
///
@@ -224,7 +261,7 @@ declare_lint_rule! {
224261
///
225262
/// ##### Example
226263
///
227-
/// ```json, options
264+
/// ```json,options
228265
/// {
229266
/// "options": {
230267
/// "hooks": [
@@ -235,7 +272,7 @@ declare_lint_rule! {
235272
/// }
236273
/// ```
237274
///
238-
/// This would enable diagnostics on the aforementioned hooks, as follows:
275+
/// This would enable diagnostics on the following code snippet:
239276
///
240277
/// ```js,options,expect_diagnostic
241278
/// function Foo() {
@@ -250,7 +287,7 @@ declare_lint_rule! {
250287
/// As previously discussed, the lint rule takes into account so-called ["stable results"](#stable-results)
251288
/// and will ensure any such variables are _not_ specified as dependencies.
252289
///
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:
254291
///
255292
/// 1. `"stableResult": true` -- marks the return value as stable. An example
256293
/// of a React hook that would be configured like this is `useRef()`.
@@ -283,7 +320,8 @@ declare_lint_rule! {
283320
///
284321
/// ### `reportUnnecessaryDependencies`
285322
///
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.
287325
///
288326
/// Default: `true`
289327
///
@@ -308,7 +346,7 @@ declare_lint_rule! {
308346
/// ### `reportMissingDependenciesArray`
309347
///
310348
/// 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.
312350
///
313351
/// Default: `false`
314352
///
@@ -323,7 +361,7 @@ declare_lint_rule! {
323361
/// ```
324362
///
325363
/// ```jsx,use_options,expect_diagnostic
326-
/// function NoArrayYesProblem() {
364+
/// function noArrayYesProblem() {
327365
/// let stateVar = 1;
328366
/// useEffect(() => {});
329367
/// }
@@ -968,8 +1006,11 @@ impl Rule for UseExhaustiveDependencies {
9681006
function_name_range,
9691007
markup! {
9701008
"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."
9731014
},
9741015
);
9751016

@@ -1001,7 +1042,13 @@ impl Rule for UseExhaustiveDependencies {
10011042
rule_category!(),
10021043
function_name_range,
10031044
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>"."
10051052
},
10061053
);
10071054

@@ -1011,7 +1058,6 @@ impl Rule for UseExhaustiveDependencies {
10111058
diag = diag.detail(
10121059
dep.syntax().text_trimmed_range(),
10131060
"Outer scope values aren't valid dependencies because mutating them doesn't re-render the component.",
1014-
10151061
);
10161062
} else {
10171063
diag = diag.detail(

0 commit comments

Comments
 (0)