[compiler] Exhaustive deps: extra tests, improve diagnostic (#35213)
First, this adds some more tests and organizes them into an `exhaustive-deps/` subdirectory. Second, the diagnostics are overhauled. For each memo block we now report a single diagnostic which summarizes the issue, plus individual errors for each missing/extra dependency. Within the extra deps, we distinguish whether it's truly extra vs whether its just a more (too) precise version of an inferred dep. For example, if you depend on `x.y.z` but the inferred dep was `x.y`. Finally, we print the full inferred deps at the end as a hint (it's also a suggestion, but this makes it more clear what would be suggested).
Joseph Savona committed
Nov 25, 2025 at 12:09 UTC
fb18ad3fd372623190a8f74387b79e28151cefc4
28 files changed
+545
-160
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+1
@@ -807,6 +807,7 @@ export type ManualMemoDependency = {
807
}
808
| {kind: 'Global'; identifierName: string};
809
path: DependencyPath;
810
+ loc: SourceLocation;
811
};
812
813
export type StartMemoize = {
compiler/packages/babel-plugin-react-compiler/src/Inference/DropManualMemoization.ts
+3
@@ -65,6 +65,7 @@ export function collectMaybeMemoDependencies(
65
identifierName: value.binding.name,
66
},
67
path: [],
68
+ loc: value.loc,
69
};
70
}
71
case 'PropertyLoad': {
@@ -74,6 +75,7 @@ export function collectMaybeMemoDependencies(
75
root: object.root,
76
// TODO: determine if the access is optional
77
path: [...object.path, {property: value.property, optional}],
78
+ loc: value.loc,
79
};
80
}
81
break;
@@ -95,6 +97,7 @@ export function collectMaybeMemoDependencies(
97
constant: false,
98
},
99
path: [],
100
+ loc: value.place.loc,
101
};
102
}
103
break;
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts
+105
-43
@@ -241,11 +241,10 @@ export function validateExhaustiveDependencies(
241
matched.add(manualDependency);
242
}
243
}
244
- const isOptionalDependency =
245
- !reactive.has(inferredDependency.identifier.id) &&
246
- (isStableType(inferredDependency.identifier) ||
247
- isPrimitiveType(inferredDependency.identifier));
248
- if (hasMatchingManualDependency || isOptionalDependency) {
244
+ if (
245
+ hasMatchingManualDependency ||
246
+ isOptionalDependency(inferredDependency, reactive)
247
+ ) {
248
continue;
249
}
250
missing.push(inferredDependency);
@@ -274,54 +273,106 @@ export function validateExhaustiveDependencies(
273
}
274
275
if (missing.length !== 0 || extra.length !== 0) {
277
- let suggestions: Array<CompilerSuggestion> | null = null;
276
+ let suggestion: CompilerSuggestion | null = null;
277
if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {
279
- suggestions = [
280
- {
281
- description: 'Update dependencies',
282
- range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
283
- op: CompilerSuggestionOperation.Replace,
284
- text: `[${inferred.map(printInferredDependency).join(', ')}]`,
285
- },
286
- ];
278
+ suggestion = {
279
+ description: 'Update dependencies',
280
+ range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
281
+ op: CompilerSuggestionOperation.Replace,
282
+ text: `[${inferred
283
+ .filter(
284
+ dep =>
285
+ dep.kind === 'Local' && !isOptionalDependency(dep, reactive),
286
+ )
287
+ .map(printInferredDependency)
288
+ .join(', ')}]`,
289
+ };
290
}
288
- if (missing.length !== 0) {
289
- const diagnostic = CompilerDiagnostic.create({
290
- category: ErrorCategory.MemoDependencies,
291
- reason: 'Found missing memoization dependencies',
292
- description:
293
- 'Missing dependencies can cause a value not to update when those inputs change, ' +
294
- 'resulting in stale UI',
295
- suggestions,
291
+ const diagnostic = CompilerDiagnostic.create({
292
+ category: ErrorCategory.MemoDependencies,
293
+ reason: 'Found missing/extra memoization dependencies',
294
+ description: [
295
+ missing.length !== 0
296
+ ? 'Missing dependencies can cause a value to update less often than it should, ' +
297
+ 'resulting in stale UI'
298
+ : null,
299
+ extra.length !== 0
300
+ ? 'Extra dependencies can cause a value to update more often than it should, ' +
301
+ 'resulting in performance problems such as excessive renders or effects firing too often'
302
+ : null,
303
+ ]
304
+ .filter(Boolean)
305
+ .join('. '),
306
+ suggestions: suggestion != null ? [suggestion] : null,
307
+ });
308
+ for (const dep of missing) {
309
+ let reactiveStableValueHint = '';
310
+ if (isStableType(dep.identifier)) {
311
+ reactiveStableValueHint =
312
+ '. Refs, setState functions, and other "stable" values generally do not need to be added ' +
313
+ 'as dependencies, but this variable may change over time to point to different values';
314
+ }
315
+ diagnostic.withDetails({
316
+ kind: 'error',
317
+ message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
318
+ loc: dep.loc,
319
});
297
- for (const dep of missing) {
298
- let reactiveStableValueHint = '';
299
- if (isStableType(dep.identifier)) {
300
- reactiveStableValueHint =
301
- '. Refs, setState functions, and other "stable" values generally do not need to be added as dependencies, but this variable may change over time to point to different values';
302
- }
320
+ }
321
+ for (const dep of extra) {
322
+ if (dep.root.kind === 'Global') {
323
diagnostic.withDetails({
324
kind: 'error',
305
- message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
306
- loc: dep.loc,
325
+ message:
326
+ `Unnecessary dependency \`${printManualMemoDependency(dep)}\`. ` +
327
+ 'Values declared outside of a component/hook should not be listed as ' +
328
+ 'dependencies as the component will not re-render if they change',
329
+ loc: dep.loc ?? startMemo.depsLoc ?? value.loc,
330
});
331
+ error.pushDiagnostic(diagnostic);
332
+ } else {
333
+ const root = dep.root.value;
334
+ const matchingInferred = inferred.find(
335
+ (
336
+ inferredDep,
337
+ ): inferredDep is Extract<InferredDependency, {kind: 'Local'}> => {
338
+ return (
339
+ inferredDep.kind === 'Local' &&
340
+ inferredDep.identifier.id === root.identifier.id &&
341
+ isSubPathIgnoringOptionals(inferredDep.path, dep.path)
342
+ );
343
+ },
344
+ );
345
+ if (
346
+ matchingInferred != null &&
347
+ !isOptionalDependency(matchingInferred, reactive)
348
+ ) {
349
+ diagnostic.withDetails({
350
+ kind: 'error',
351
+ message:
352
+ `Overly precise dependency \`${printManualMemoDependency(dep)}\`, ` +
353
+ `use \`${printInferredDependency(matchingInferred)}\` instead`,
354
+ loc: dep.loc ?? startMemo.depsLoc ?? value.loc,
355
+ });
356
+ } else {
357
+ /**
358
+ * Else this dependency doesn't correspond to anything referenced in the memo function,
359
+ * or is an optional dependency so we don't want to suggest adding it
360
+ */
361
+ diagnostic.withDetails({
362
+ kind: 'error',
363
+ message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\``,
364
+ loc: dep.loc ?? startMemo.depsLoc ?? value.loc,
365
+ });
366
+ }
367
}
309
- error.pushDiagnostic(diagnostic);
310
- } else if (extra.length !== 0) {
311
- const diagnostic = CompilerDiagnostic.create({
312
- category: ErrorCategory.MemoDependencies,
313
- reason: 'Found unnecessary memoization dependencies',
314
- description:
315
- 'Unnecessary dependencies can cause a value to update more often than necessary, ' +
316
- 'causing performance regressions and effects to fire more often than expected',
317
- });
368
+ }
369
+ if (suggestion != null) {
370
diagnostic.withDetails({
319
- kind: 'error',
320
- message: `Unnecessary dependencies ${extra.map(dep => `\`${printManualMemoDependency(dep)}\``).join(', ')}`,
321
- loc: startMemo.depsLoc ?? value.loc,
371
+ kind: 'hint',
372
+ message: `Inferred dependencies: \`${suggestion.text}\``,
373
});
323
- error.pushDiagnostic(diagnostic);
374
}
375
+ error.pushDiagnostic(diagnostic);
376
}
377
378
dependencies.clear();
@@ -826,3 +877,14 @@ export function findOptionalPlaces(
877
}
878
return optionals;
879
}
880
+
881
+function isOptionalDependency(
882
+ inferredDependency: Extract<InferredDependency, {kind: 'Local'}>,
883
+ reactive: Set<IdentifierId>,
884
+): boolean {
885
+ return (
886
+ !reactive.has(inferredDependency.identifier.id) &&
887
+ (isStableType(inferredDependency.identifier) ||
888
+ isPrimitiveType(inferredDependency.identifier))
889
+ );
890
+}
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts
+4
@@ -242,6 +242,7 @@ function validateInferredDep(
242
normalizedDep = {
243
root: maybeNormalizedRoot.root,
244
path: [...maybeNormalizedRoot.path, ...dep.path],
245
+ loc: maybeNormalizedRoot.loc,
246
};
247
} else {
248
CompilerError.invariant(dep.identifier.name?.kind === 'named', {
@@ -270,6 +271,7 @@ function validateInferredDep(
271
constant: false,
272
},
273
path: [...dep.path],
274
+ loc: GeneratedSource,
275
};
276
}
277
for (const decl of declsWithinMemoBlock) {
@@ -383,6 +385,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
385
constant: false,
386
},
387
path: [],
388
+ loc: storeTarget.loc,
389
});
390
}
391
}
@@ -413,6 +416,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
416
constant: false,
417
},
418
path: [],
419
+ loc: lvalue.loc,
420
});
421
}
422
}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-exhaustive-deps.expect.md
deleted
-109
@@ -1,109 +0,0 @@
1
-
2
-## Input
3
-
4
-```javascript
5
-// @validateExhaustiveMemoizationDependencies
6
-import {useMemo} from 'react';
7
-import {Stringify} from 'shared-runtime';
8
-
9
-function Component({x, y, z}) {
10
- const a = useMemo(() => {
11
- return x?.y.z?.a;
12
- // error: too precise
13
- }, [x?.y.z?.a.b]);
14
- const b = useMemo(() => {
15
- return x.y.z?.a;
16
- // ok, not our job to type check nullability
17
- }, [x.y.z.a]);
18
- const c = useMemo(() => {
19
- return x?.y.z.a?.b;
20
- // error: too precise
21
- }, [x?.y.z.a?.b.z]);
22
- const d = useMemo(() => {
23
- return x?.y?.[(console.log(y), z?.b)];
24
- // ok
25
- }, [x?.y, y, z?.b]);
26
- const e = useMemo(() => {
27
- const e = [];
28
- e.push(x);
29
- return e;
30
- // ok
31
- }, [x]);
32
- const f = useMemo(() => {
33
- return [];
34
- // error: unnecessary
35
- }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
36
- const ref1 = useRef(null);
37
- const ref2 = useRef(null);
38
- const ref = z ? ref1 : ref2;
39
- const cb = useMemo(() => {
40
- return () => {
41
- return ref.current;
42
- };
43
- // error: ref is a stable type but reactive
44
- }, []);
45
- return <Stringify results={[a, b, c, d, e, f, cb]} />;
46
-}
47
-
48
-```
49
-
50
-
51
-## Error
52
-
53
-```
54
-Found 4 errors:
55
-
56
-Error: Found missing memoization dependencies
57
-
58
-Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
59
-
60
-error.invalid-exhaustive-deps.ts:7:11
61
- 5 | function Component({x, y, z}) {
62
- 6 | const a = useMemo(() => {
63
-> 7 | return x?.y.z?.a;
64
- | ^^^^^^^^^ Missing dependency `x?.y.z?.a`
65
- 8 | // error: too precise
66
- 9 | }, [x?.y.z?.a.b]);
67
- 10 | const b = useMemo(() => {
68
-
69
-Error: Found missing memoization dependencies
70
-
71
-Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
72
-
73
-error.invalid-exhaustive-deps.ts:15:11
74
- 13 | }, [x.y.z.a]);
75
- 14 | const c = useMemo(() => {
76
-> 15 | return x?.y.z.a?.b;
77
- | ^^^^^^^^^^^ Missing dependency `x?.y.z.a?.b`
78
- 16 | // error: too precise
79
- 17 | }, [x?.y.z.a?.b.z]);
80
- 18 | const d = useMemo(() => {
81
-
82
-Error: Found unnecessary memoization dependencies
83
-
84
-Unnecessary dependencies can cause a value to update more often than necessary, causing performance regressions and effects to fire more often than expected.
85
-
86
-error.invalid-exhaustive-deps.ts:31:5
87
- 29 | return [];
88
- 30 | // error: unnecessary
89
-> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
90
- | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unnecessary dependencies `x`, `y.z`, `z?.y?.a`, `UNUSED_GLOBAL`
91
- 32 | const ref1 = useRef(null);
92
- 33 | const ref2 = useRef(null);
93
- 34 | const ref = z ? ref1 : ref2;
94
-
95
-Error: Found missing memoization dependencies
96
-
97
-Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
98
-
99
-error.invalid-exhaustive-deps.ts:37:13
100
- 35 | const cb = useMemo(() => {
101
- 36 | return () => {
102
-> 37 | return ref.current;
103
- | ^^^ Missing dependency `ref`. Refs, setState functions, and other "stable" values generally do not need to be added as dependencies, but this variable may change over time to point to different values
104
- 38 | };
105
- 39 | // error: ref is a stable type but reactive
106
- 40 | }, []);
107
-```
108
-
109
-
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/compile-files-with-exhaustive-deps-violation-in-effects.expect.md
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/compile-files-with-exhaustive-deps-violation-in-effects.js
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-dep-on-ref-current-value.expect.md
new
+40
@@ -0,0 +1,40 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateExhaustiveMemoizationDependencies
6
+
7
+function Component() {
8
+ const ref = useRef(null);
9
+ const onChange = useCallback(() => {
10
+ return ref.current.value;
11
+ }, [ref.current.value]);
12
+
13
+ return <input ref={ref} onChange={onChange} />;
14
+}
15
+
16
+```
17
+
18
+
19
+## Error
20
+
21
+```
22
+Found 1 error:
23
+
24
+Error: Found missing/extra memoization dependencies
25
+
26
+Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
27
+
28
+error.invalid-dep-on-ref-current-value.ts:7:6
29
+ 5 | const onChange = useCallback(() => {
30
+ 6 | return ref.current.value;
31
+> 7 | }, [ref.current.value]);
32
+ | ^^^^^^^^^^^^^^^^^ Unnecessary dependency `ref.current.value`
33
+ 8 |
34
+ 9 | return <input ref={ref} onChange={onChange} />;
35
+ 10 | }
36
+
37
+Inferred dependencies: `[]`
38
+```
39
+
40
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-dep-on-ref-current-value.js
new
+10
@@ -0,0 +1,10 @@
1
+// @validateExhaustiveMemoizationDependencies
2
+
3
+function Component() {
4
+ const ref = useRef(null);
5
+ const onChange = useCallback(() => {
6
+ return ref.current.value;
7
+ }, [ref.current.value]);
8
+
9
+ return <input ref={ref} onChange={onChange} />;
10
+}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-exhaustive-deps-disallow-unused-stable-types.expect.md
renamed
+6
-4
@@ -25,18 +25,20 @@ function Component() {
25
```
26
Found 1 error:
27
28
-Error: Found unnecessary memoization dependencies
28
+Error: Found missing/extra memoization dependencies
29
30
-Unnecessary dependencies can cause a value to update more often than necessary, causing performance regressions and effects to fire more often than expected.
30
+Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
31
32
-error.invalid-exhaustive-deps-disallow-unused-stable-types.ts:11:5
32
+error.invalid-exhaustive-deps-disallow-unused-stable-types.ts:11:13
33
9 | return [state];
34
10 | // error: `setState` is a stable type, but not actually referenced
35
> 11 | }, [state, setState]);
36
- | ^^^^^^^^^^^^^^^^^ Unnecessary dependencies `setState`
36
+ | ^^^^^^^^ Unnecessary dependency `setState`
37
12 |
38
13 | return 'oops';
39
14 | }
40
+
41
+Inferred dependencies: `[state]`
42
```
43
44
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-exhaustive-deps-disallow-unused-stable-types.js
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-exhaustive-deps.expect.md
new
+204
@@ -0,0 +1,204 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateExhaustiveMemoizationDependencies
6
+import {useMemo} from 'react';
7
+import {Stringify} from 'shared-runtime';
8
+
9
+function Component({x, y, z}) {
10
+ const a = useMemo(() => {
11
+ return x?.y.z?.a;
12
+ // error: too precise
13
+ }, [x?.y.z?.a.b]);
14
+ const b = useMemo(() => {
15
+ return x.y.z?.a;
16
+ // ok, not our job to type check nullability
17
+ }, [x.y.z.a]);
18
+ const c = useMemo(() => {
19
+ return x?.y.z.a?.b;
20
+ // error: too precise
21
+ }, [x?.y.z.a?.b.z]);
22
+ const d = useMemo(() => {
23
+ return x?.y?.[(console.log(y), z?.b)];
24
+ // ok
25
+ }, [x?.y, y, z?.b]);
26
+ const e = useMemo(() => {
27
+ const e = [];
28
+ e.push(x);
29
+ return e;
30
+ // ok
31
+ }, [x]);
32
+ const f = useMemo(() => {
33
+ return [];
34
+ // error: unnecessary
35
+ }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
36
+ const ref1 = useRef(null);
37
+ const ref2 = useRef(null);
38
+ const ref = z ? ref1 : ref2;
39
+ const cb = useMemo(() => {
40
+ return () => {
41
+ return ref.current;
42
+ };
43
+ // error: ref is a stable type but reactive
44
+ }, []);
45
+ return <Stringify results={[a, b, c, d, e, f, cb]} />;
46
+}
47
+
48
+```
49
+
50
+
51
+## Error
52
+
53
+```
54
+Found 5 errors:
55
+
56
+Error: Found missing/extra memoization dependencies
57
+
58
+Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
59
+
60
+error.invalid-exhaustive-deps.ts:7:11
61
+ 5 | function Component({x, y, z}) {
62
+ 6 | const a = useMemo(() => {
63
+> 7 | return x?.y.z?.a;
64
+ | ^^^^^^^^^ Missing dependency `x?.y.z?.a`
65
+ 8 | // error: too precise
66
+ 9 | }, [x?.y.z?.a.b]);
67
+ 10 | const b = useMemo(() => {
68
+
69
+error.invalid-exhaustive-deps.ts:9:6
70
+ 7 | return x?.y.z?.a;
71
+ 8 | // error: too precise
72
+> 9 | }, [x?.y.z?.a.b]);
73
+ | ^^^^^^^^^^^ Overly precise dependency `x?.y.z?.a.b`, use `x?.y.z?.a` instead
74
+ 10 | const b = useMemo(() => {
75
+ 11 | return x.y.z?.a;
76
+ 12 | // ok, not our job to type check nullability
77
+
78
+Inferred dependencies: `[x?.y.z?.a]`
79
+
80
+Error: Found missing/extra memoization dependencies
81
+
82
+Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
83
+
84
+error.invalid-exhaustive-deps.ts:15:11
85
+ 13 | }, [x.y.z.a]);
86
+ 14 | const c = useMemo(() => {
87
+> 15 | return x?.y.z.a?.b;
88
+ | ^^^^^^^^^^^ Missing dependency `x?.y.z.a?.b`
89
+ 16 | // error: too precise
90
+ 17 | }, [x?.y.z.a?.b.z]);
91
+ 18 | const d = useMemo(() => {
92
+
93
+error.invalid-exhaustive-deps.ts:17:6
94
+ 15 | return x?.y.z.a?.b;
95
+ 16 | // error: too precise
96
+> 17 | }, [x?.y.z.a?.b.z]);
97
+ | ^^^^^^^^^^^^^ Overly precise dependency `x?.y.z.a?.b.z`, use `x?.y.z.a?.b` instead
98
+ 18 | const d = useMemo(() => {
99
+ 19 | return x?.y?.[(console.log(y), z?.b)];
100
+ 20 | // ok
101
+
102
+Inferred dependencies: `[x?.y.z.a?.b]`
103
+
104
+Error: Found missing/extra memoization dependencies
105
+
106
+Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
107
+
108
+error.invalid-exhaustive-deps.ts:31:6
109
+ 29 | return [];
110
+ 30 | // error: unnecessary
111
+> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
112
+ | ^ Unnecessary dependency `x`
113
+ 32 | const ref1 = useRef(null);
114
+ 33 | const ref2 = useRef(null);
115
+ 34 | const ref = z ? ref1 : ref2;
116
+
117
+error.invalid-exhaustive-deps.ts:31:9
118
+ 29 | return [];
119
+ 30 | // error: unnecessary
120
+> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
121
+ | ^^^ Unnecessary dependency `y.z`
122
+ 32 | const ref1 = useRef(null);
123
+ 33 | const ref2 = useRef(null);
124
+ 34 | const ref = z ? ref1 : ref2;
125
+
126
+error.invalid-exhaustive-deps.ts:31:14
127
+ 29 | return [];
128
+ 30 | // error: unnecessary
129
+> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
130
+ | ^^^^^^^ Unnecessary dependency `z?.y?.a`
131
+ 32 | const ref1 = useRef(null);
132
+ 33 | const ref2 = useRef(null);
133
+ 34 | const ref = z ? ref1 : ref2;
134
+
135
+error.invalid-exhaustive-deps.ts:31:23
136
+ 29 | return [];
137
+ 30 | // error: unnecessary
138
+> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
139
+ | ^^^^^^^^^^^^^ Unnecessary dependency `UNUSED_GLOBAL`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
140
+ 32 | const ref1 = useRef(null);
141
+ 33 | const ref2 = useRef(null);
142
+ 34 | const ref = z ? ref1 : ref2;
143
+
144
+Inferred dependencies: `[]`
145
+
146
+Error: Found missing/extra memoization dependencies
147
+
148
+Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
149
+
150
+error.invalid-exhaustive-deps.ts:31:6
151
+ 29 | return [];
152
+ 30 | // error: unnecessary
153
+> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
154
+ | ^ Unnecessary dependency `x`
155
+ 32 | const ref1 = useRef(null);
156
+ 33 | const ref2 = useRef(null);
157
+ 34 | const ref = z ? ref1 : ref2;
158
+
159
+error.invalid-exhaustive-deps.ts:31:9
160
+ 29 | return [];
161
+ 30 | // error: unnecessary
162
+> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
163
+ | ^^^ Unnecessary dependency `y.z`
164
+ 32 | const ref1 = useRef(null);
165
+ 33 | const ref2 = useRef(null);
166
+ 34 | const ref = z ? ref1 : ref2;
167
+
168
+error.invalid-exhaustive-deps.ts:31:14
169
+ 29 | return [];
170
+ 30 | // error: unnecessary
171
+> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
172
+ | ^^^^^^^ Unnecessary dependency `z?.y?.a`
173
+ 32 | const ref1 = useRef(null);
174
+ 33 | const ref2 = useRef(null);
175
+ 34 | const ref = z ? ref1 : ref2;
176
+
177
+error.invalid-exhaustive-deps.ts:31:23
178
+ 29 | return [];
179
+ 30 | // error: unnecessary
180
+> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
181
+ | ^^^^^^^^^^^^^ Unnecessary dependency `UNUSED_GLOBAL`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
182
+ 32 | const ref1 = useRef(null);
183
+ 33 | const ref2 = useRef(null);
184
+ 34 | const ref = z ? ref1 : ref2;
185
+
186
+Inferred dependencies: `[]`
187
+
188
+Error: Found missing/extra memoization dependencies
189
+
190
+Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
191
+
192
+error.invalid-exhaustive-deps.ts:37:13
193
+ 35 | const cb = useMemo(() => {
194
+ 36 | return () => {
195
+> 37 | return ref.current;
196
+ | ^^^ Missing dependency `ref`. Refs, setState functions, and other "stable" values generally do not need to be added as dependencies, but this variable may change over time to point to different values
197
+ 38 | };
198
+ 39 | // error: ref is a stable type but reactive
199
+ 40 | }, []);
200
+
201
+Inferred dependencies: `[ref]`
202
+```
203
+
204
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-exhaustive-deps.js
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep-inner-function.expect.md
new
+43
@@ -0,0 +1,43 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateExhaustiveMemoizationDependencies
6
+
7
+import {useMemo} from 'react';
8
+import {makeObject_Primitives} from 'shared-runtime';
9
+
10
+function useHook() {
11
+ const object = makeObject_Primitives();
12
+ const fn = useCallback(() => {
13
+ const g = () => {
14
+ return [object];
15
+ };
16
+ return g;
17
+ });
18
+ return fn;
19
+}
20
+
21
+```
22
+
23
+
24
+## Error
25
+
26
+```
27
+Found 1 error:
28
+
29
+Error: Found missing/extra memoization dependencies
30
+
31
+Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
32
+
33
+error.invalid-missing-nonreactive-dep-inner-function.ts:10:14
34
+ 8 | const fn = useCallback(() => {
35
+ 9 | const g = () => {
36
+> 10 | return [object];
37
+ | ^^^^^^ Missing dependency `object`
38
+ 11 | };
39
+ 12 | return g;
40
+ 13 | });
41
+```
42
+
43
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep-inner-function.js
new
+15
@@ -0,0 +1,15 @@
1
+// @validateExhaustiveMemoizationDependencies
2
+
3
+import {useMemo} from 'react';
4
+import {makeObject_Primitives} from 'shared-runtime';
5
+
6
+function useHook() {
7
+ const object = makeObject_Primitives();
8
+ const fn = useCallback(() => {
9
+ const g = () => {
10
+ return [object];
11
+ };
12
+ return g;
13
+ });
14
+ return fn;
15
+}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep-unmemoized.expect.md
new
+43
@@ -0,0 +1,43 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateExhaustiveMemoizationDependencies
6
+
7
+import {useMemo} from 'react';
8
+import {makeObject_Primitives, useIdentity} from 'shared-runtime';
9
+
10
+function useHook() {
11
+ // object is non-reactive but not memoized bc the mutation surrounds a hook
12
+ const object = makeObject_Primitives();
13
+ useIdentity();
14
+ object.x = 0;
15
+ const array = useMemo(() => [object], []);
16
+ return array;
17
+}
18
+
19
+```
20
+
21
+
22
+## Error
23
+
24
+```
25
+Found 1 error:
26
+
27
+Error: Found missing/extra memoization dependencies
28
+
29
+Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
30
+
31
+error.invalid-missing-nonreactive-dep-unmemoized.ts:11:31
32
+ 9 | useIdentity();
33
+ 10 | object.x = 0;
34
+> 11 | const array = useMemo(() => [object], []);
35
+ | ^^^^^^ Missing dependency `object`
36
+ 12 | return array;
37
+ 13 | }
38
+ 14 |
39
+
40
+Inferred dependencies: `[object]`
41
+```
42
+
43
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep-unmemoized.js
new
+13
@@ -0,0 +1,13 @@
1
+// @validateExhaustiveMemoizationDependencies
2
+
3
+import {useMemo} from 'react';
4
+import {makeObject_Primitives, useIdentity} from 'shared-runtime';
5
+
6
+function useHook() {
7
+ // object is non-reactive but not memoized bc the mutation surrounds a hook
8
+ const object = makeObject_Primitives();
9
+ useIdentity();
10
+ object.x = 0;
11
+ const array = useMemo(() => [object], []);
12
+ return array;
13
+}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep.expect.md
new
+40
@@ -0,0 +1,40 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+// @validateExhaustiveMemoizationDependencies
6
+
7
+import {useMemo} from 'react';
8
+import {makeObject_Primitives} from 'shared-runtime';
9
+
10
+function useHook() {
11
+ const object = makeObject_Primitives();
12
+ const array = useMemo(() => [object], []);
13
+ return array;
14
+}
15
+
16
+```
17
+
18
+
19
+## Error
20
+
21
+```
22
+Found 1 error:
23
+
24
+Error: Found missing/extra memoization dependencies
25
+
26
+Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
27
+
28
+error.invalid-missing-nonreactive-dep.ts:8:31
29
+ 6 | function useHook() {
30
+ 7 | const object = makeObject_Primitives();
31
+> 8 | const array = useMemo(() => [object], []);
32
+ | ^^^^^^ Missing dependency `object`
33
+ 9 | return array;
34
+ 10 | }
35
+ 11 |
36
+
37
+Inferred dependencies: `[object]`
38
+```
39
+
40
+
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep.js
new
+10
@@ -0,0 +1,10 @@
1
+// @validateExhaustiveMemoizationDependencies
2
+
3
+import {useMemo} from 'react';
4
+import {makeObject_Primitives} from 'shared-runtime';
5
+
6
+function useHook() {
7
+ const object = makeObject_Primitives();
8
+ const array = useMemo(() => [object], []);
9
+ return array;
10
+}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.sketchy-code-exhaustive-deps.expect.md
renamed
+4
-2
@@ -25,9 +25,9 @@ function Component() {
25
```
26
Found 1 error:
27
28
-Error: Found missing memoization dependencies
28
+Error: Found missing/extra memoization dependencies
29
30
-Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
30
+Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
31
32
error.sketchy-code-exhaustive-deps.ts:8:16
33
6 | const foo = useCallback(
@@ -37,6 +37,8 @@ error.sketchy-code-exhaustive-deps.ts:8:16
37
9 | }, // eslint-disable-next-line react-hooks/exhaustive-deps
38
10 | []
39
11 | );
40
+
41
+Inferred dependencies: `[item]`
42
```
43
44
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.sketchy-code-exhaustive-deps.js
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/exhaustive-deps-allow-constant-folded-values.expect.md
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/exhaustive-deps-allow-constant-folded-values.js
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/exhaustive-deps-allow-nonreactive-stable-types-as-extra-deps.expect.md
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/exhaustive-deps-allow-nonreactive-stable-types-as-extra-deps.js
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/exhaustive-deps.expect.md
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/exhaustive-deps.js
renamed
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-unrelated-mutation-in-depslist.expect.md
+4
-2
@@ -32,9 +32,9 @@ function useFoo(input1) {
32
```
33
Found 1 error:
34
35
-Error: Found missing memoization dependencies
35
+Error: Found missing/extra memoization dependencies
36
37
-Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
37
+Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
38
39
error.useMemo-unrelated-mutation-in-depslist.ts:18:14
40
16 | const memoized = useMemo(() => {
@@ -44,6 +44,8 @@ error.useMemo-unrelated-mutation-in-depslist.ts:18:14
44
19 |
45
20 | return [x, memoized];
46
21 | }
47
+
48
+Inferred dependencies: `[x, y]`
49
```
50
51
\ No newline at end of file