mirror of
https://github.com/facebook/react.git
synced 2026-02-26 07:25:30 +00:00
[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).
This commit is contained in:
@@ -807,6 +807,7 @@ export type ManualMemoDependency = {
|
||||
}
|
||||
| {kind: 'Global'; identifierName: string};
|
||||
path: DependencyPath;
|
||||
loc: SourceLocation;
|
||||
};
|
||||
|
||||
export type StartMemoize = {
|
||||
|
||||
@@ -65,6 +65,7 @@ export function collectMaybeMemoDependencies(
|
||||
identifierName: value.binding.name,
|
||||
},
|
||||
path: [],
|
||||
loc: value.loc,
|
||||
};
|
||||
}
|
||||
case 'PropertyLoad': {
|
||||
@@ -74,6 +75,7 @@ export function collectMaybeMemoDependencies(
|
||||
root: object.root,
|
||||
// TODO: determine if the access is optional
|
||||
path: [...object.path, {property: value.property, optional}],
|
||||
loc: value.loc,
|
||||
};
|
||||
}
|
||||
break;
|
||||
@@ -95,6 +97,7 @@ export function collectMaybeMemoDependencies(
|
||||
constant: false,
|
||||
},
|
||||
path: [],
|
||||
loc: value.place.loc,
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -241,11 +241,10 @@ export function validateExhaustiveDependencies(
|
||||
matched.add(manualDependency);
|
||||
}
|
||||
}
|
||||
const isOptionalDependency =
|
||||
!reactive.has(inferredDependency.identifier.id) &&
|
||||
(isStableType(inferredDependency.identifier) ||
|
||||
isPrimitiveType(inferredDependency.identifier));
|
||||
if (hasMatchingManualDependency || isOptionalDependency) {
|
||||
if (
|
||||
hasMatchingManualDependency ||
|
||||
isOptionalDependency(inferredDependency, reactive)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
missing.push(inferredDependency);
|
||||
@@ -274,54 +273,106 @@ export function validateExhaustiveDependencies(
|
||||
}
|
||||
|
||||
if (missing.length !== 0 || extra.length !== 0) {
|
||||
let suggestions: Array<CompilerSuggestion> | null = null;
|
||||
let suggestion: CompilerSuggestion | null = null;
|
||||
if (startMemo.depsLoc != null && typeof startMemo.depsLoc !== 'symbol') {
|
||||
suggestions = [
|
||||
{
|
||||
description: 'Update dependencies',
|
||||
range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
|
||||
op: CompilerSuggestionOperation.Replace,
|
||||
text: `[${inferred.map(printInferredDependency).join(', ')}]`,
|
||||
},
|
||||
];
|
||||
suggestion = {
|
||||
description: 'Update dependencies',
|
||||
range: [startMemo.depsLoc.start.index, startMemo.depsLoc.end.index],
|
||||
op: CompilerSuggestionOperation.Replace,
|
||||
text: `[${inferred
|
||||
.filter(
|
||||
dep =>
|
||||
dep.kind === 'Local' && !isOptionalDependency(dep, reactive),
|
||||
)
|
||||
.map(printInferredDependency)
|
||||
.join(', ')}]`,
|
||||
};
|
||||
}
|
||||
if (missing.length !== 0) {
|
||||
const diagnostic = CompilerDiagnostic.create({
|
||||
category: ErrorCategory.MemoDependencies,
|
||||
reason: 'Found missing memoization dependencies',
|
||||
description:
|
||||
'Missing dependencies can cause a value not to update when those inputs change, ' +
|
||||
'resulting in stale UI',
|
||||
suggestions,
|
||||
});
|
||||
for (const dep of missing) {
|
||||
let reactiveStableValueHint = '';
|
||||
if (isStableType(dep.identifier)) {
|
||||
reactiveStableValueHint =
|
||||
'. 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';
|
||||
}
|
||||
diagnostic.withDetails({
|
||||
kind: 'error',
|
||||
message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
|
||||
loc: dep.loc,
|
||||
});
|
||||
const diagnostic = CompilerDiagnostic.create({
|
||||
category: ErrorCategory.MemoDependencies,
|
||||
reason: 'Found missing/extra memoization dependencies',
|
||||
description: [
|
||||
missing.length !== 0
|
||||
? 'Missing dependencies can cause a value to update less often than it should, ' +
|
||||
'resulting in stale UI'
|
||||
: null,
|
||||
extra.length !== 0
|
||||
? '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'
|
||||
: null,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join('. '),
|
||||
suggestions: suggestion != null ? [suggestion] : null,
|
||||
});
|
||||
for (const dep of missing) {
|
||||
let reactiveStableValueHint = '';
|
||||
if (isStableType(dep.identifier)) {
|
||||
reactiveStableValueHint =
|
||||
'. 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';
|
||||
}
|
||||
error.pushDiagnostic(diagnostic);
|
||||
} else if (extra.length !== 0) {
|
||||
const diagnostic = CompilerDiagnostic.create({
|
||||
category: ErrorCategory.MemoDependencies,
|
||||
reason: 'Found unnecessary memoization dependencies',
|
||||
description:
|
||||
'Unnecessary dependencies can cause a value to update more often than necessary, ' +
|
||||
'causing performance regressions and effects to fire more often than expected',
|
||||
});
|
||||
diagnostic.withDetails({
|
||||
kind: 'error',
|
||||
message: `Unnecessary dependencies ${extra.map(dep => `\`${printManualMemoDependency(dep)}\``).join(', ')}`,
|
||||
loc: startMemo.depsLoc ?? value.loc,
|
||||
message: `Missing dependency \`${printInferredDependency(dep)}\`${reactiveStableValueHint}`,
|
||||
loc: dep.loc,
|
||||
});
|
||||
error.pushDiagnostic(diagnostic);
|
||||
}
|
||||
for (const dep of extra) {
|
||||
if (dep.root.kind === 'Global') {
|
||||
diagnostic.withDetails({
|
||||
kind: 'error',
|
||||
message:
|
||||
`Unnecessary dependency \`${printManualMemoDependency(dep)}\`. ` +
|
||||
'Values declared outside of a component/hook should not be listed as ' +
|
||||
'dependencies as the component will not re-render if they change',
|
||||
loc: dep.loc ?? startMemo.depsLoc ?? value.loc,
|
||||
});
|
||||
error.pushDiagnostic(diagnostic);
|
||||
} else {
|
||||
const root = dep.root.value;
|
||||
const matchingInferred = inferred.find(
|
||||
(
|
||||
inferredDep,
|
||||
): inferredDep is Extract<InferredDependency, {kind: 'Local'}> => {
|
||||
return (
|
||||
inferredDep.kind === 'Local' &&
|
||||
inferredDep.identifier.id === root.identifier.id &&
|
||||
isSubPathIgnoringOptionals(inferredDep.path, dep.path)
|
||||
);
|
||||
},
|
||||
);
|
||||
if (
|
||||
matchingInferred != null &&
|
||||
!isOptionalDependency(matchingInferred, reactive)
|
||||
) {
|
||||
diagnostic.withDetails({
|
||||
kind: 'error',
|
||||
message:
|
||||
`Overly precise dependency \`${printManualMemoDependency(dep)}\`, ` +
|
||||
`use \`${printInferredDependency(matchingInferred)}\` instead`,
|
||||
loc: dep.loc ?? startMemo.depsLoc ?? value.loc,
|
||||
});
|
||||
} else {
|
||||
/**
|
||||
* Else this dependency doesn't correspond to anything referenced in the memo function,
|
||||
* or is an optional dependency so we don't want to suggest adding it
|
||||
*/
|
||||
diagnostic.withDetails({
|
||||
kind: 'error',
|
||||
message: `Unnecessary dependency \`${printManualMemoDependency(dep)}\``,
|
||||
loc: dep.loc ?? startMemo.depsLoc ?? value.loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (suggestion != null) {
|
||||
diagnostic.withDetails({
|
||||
kind: 'hint',
|
||||
message: `Inferred dependencies: \`${suggestion.text}\``,
|
||||
});
|
||||
}
|
||||
error.pushDiagnostic(diagnostic);
|
||||
}
|
||||
|
||||
dependencies.clear();
|
||||
@@ -826,3 +877,14 @@ export function findOptionalPlaces(
|
||||
}
|
||||
return optionals;
|
||||
}
|
||||
|
||||
function isOptionalDependency(
|
||||
inferredDependency: Extract<InferredDependency, {kind: 'Local'}>,
|
||||
reactive: Set<IdentifierId>,
|
||||
): boolean {
|
||||
return (
|
||||
!reactive.has(inferredDependency.identifier.id) &&
|
||||
(isStableType(inferredDependency.identifier) ||
|
||||
isPrimitiveType(inferredDependency.identifier))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -242,6 +242,7 @@ function validateInferredDep(
|
||||
normalizedDep = {
|
||||
root: maybeNormalizedRoot.root,
|
||||
path: [...maybeNormalizedRoot.path, ...dep.path],
|
||||
loc: maybeNormalizedRoot.loc,
|
||||
};
|
||||
} else {
|
||||
CompilerError.invariant(dep.identifier.name?.kind === 'named', {
|
||||
@@ -270,6 +271,7 @@ function validateInferredDep(
|
||||
constant: false,
|
||||
},
|
||||
path: [...dep.path],
|
||||
loc: GeneratedSource,
|
||||
};
|
||||
}
|
||||
for (const decl of declsWithinMemoBlock) {
|
||||
@@ -383,6 +385,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
constant: false,
|
||||
},
|
||||
path: [],
|
||||
loc: storeTarget.loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -413,6 +416,7 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
|
||||
constant: false,
|
||||
},
|
||||
path: [],
|
||||
loc: lvalue.loc,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
import {useMemo} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
const a = useMemo(() => {
|
||||
return x?.y.z?.a;
|
||||
// error: too precise
|
||||
}, [x?.y.z?.a.b]);
|
||||
const b = useMemo(() => {
|
||||
return x.y.z?.a;
|
||||
// ok, not our job to type check nullability
|
||||
}, [x.y.z.a]);
|
||||
const c = useMemo(() => {
|
||||
return x?.y.z.a?.b;
|
||||
// error: too precise
|
||||
}, [x?.y.z.a?.b.z]);
|
||||
const d = useMemo(() => {
|
||||
return x?.y?.[(console.log(y), z?.b)];
|
||||
// ok
|
||||
}, [x?.y, y, z?.b]);
|
||||
const e = useMemo(() => {
|
||||
const e = [];
|
||||
e.push(x);
|
||||
return e;
|
||||
// ok
|
||||
}, [x]);
|
||||
const f = useMemo(() => {
|
||||
return [];
|
||||
// error: unnecessary
|
||||
}, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
const ref1 = useRef(null);
|
||||
const ref2 = useRef(null);
|
||||
const ref = z ? ref1 : ref2;
|
||||
const cb = useMemo(() => {
|
||||
return () => {
|
||||
return ref.current;
|
||||
};
|
||||
// error: ref is a stable type but reactive
|
||||
}, []);
|
||||
return <Stringify results={[a, b, c, d, e, f, cb]} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 4 errors:
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:7:11
|
||||
5 | function Component({x, y, z}) {
|
||||
6 | const a = useMemo(() => {
|
||||
> 7 | return x?.y.z?.a;
|
||||
| ^^^^^^^^^ Missing dependency `x?.y.z?.a`
|
||||
8 | // error: too precise
|
||||
9 | }, [x?.y.z?.a.b]);
|
||||
10 | const b = useMemo(() => {
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:15:11
|
||||
13 | }, [x.y.z.a]);
|
||||
14 | const c = useMemo(() => {
|
||||
> 15 | return x?.y.z.a?.b;
|
||||
| ^^^^^^^^^^^ Missing dependency `x?.y.z.a?.b`
|
||||
16 | // error: too precise
|
||||
17 | }, [x?.y.z.a?.b.z]);
|
||||
18 | const d = useMemo(() => {
|
||||
|
||||
Error: Found unnecessary memoization dependencies
|
||||
|
||||
Unnecessary dependencies can cause a value to update more often than necessary, causing performance regressions and effects to fire more often than expected.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:31:5
|
||||
29 | return [];
|
||||
30 | // error: unnecessary
|
||||
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unnecessary dependencies `x`, `y.z`, `z?.y?.a`, `UNUSED_GLOBAL`
|
||||
32 | const ref1 = useRef(null);
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:37:13
|
||||
35 | const cb = useMemo(() => {
|
||||
36 | return () => {
|
||||
> 37 | return ref.current;
|
||||
| ^^^ 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
|
||||
38 | };
|
||||
39 | // error: ref is a stable type but reactive
|
||||
40 | }, []);
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
const onChange = useCallback(() => {
|
||||
return ref.current.value;
|
||||
}, [ref.current.value]);
|
||||
|
||||
return <input ref={ref} onChange={onChange} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
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.
|
||||
|
||||
error.invalid-dep-on-ref-current-value.ts:7:6
|
||||
5 | const onChange = useCallback(() => {
|
||||
6 | return ref.current.value;
|
||||
> 7 | }, [ref.current.value]);
|
||||
| ^^^^^^^^^^^^^^^^^ Unnecessary dependency `ref.current.value`
|
||||
8 |
|
||||
9 | return <input ref={ref} onChange={onChange} />;
|
||||
10 | }
|
||||
|
||||
Inferred dependencies: `[]`
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
|
||||
function Component() {
|
||||
const ref = useRef(null);
|
||||
const onChange = useCallback(() => {
|
||||
return ref.current.value;
|
||||
}, [ref.current.value]);
|
||||
|
||||
return <input ref={ref} onChange={onChange} />;
|
||||
}
|
||||
@@ -25,18 +25,20 @@ function Component() {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Found unnecessary memoization dependencies
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
Unnecessary dependencies can cause a value to update more often than necessary, causing performance regressions and effects to fire more often than expected.
|
||||
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.
|
||||
|
||||
error.invalid-exhaustive-deps-disallow-unused-stable-types.ts:11:5
|
||||
error.invalid-exhaustive-deps-disallow-unused-stable-types.ts:11:13
|
||||
9 | return [state];
|
||||
10 | // error: `setState` is a stable type, but not actually referenced
|
||||
> 11 | }, [state, setState]);
|
||||
| ^^^^^^^^^^^^^^^^^ Unnecessary dependencies `setState`
|
||||
| ^^^^^^^^ Unnecessary dependency `setState`
|
||||
12 |
|
||||
13 | return 'oops';
|
||||
14 | }
|
||||
|
||||
Inferred dependencies: `[state]`
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,204 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
import {useMemo} from 'react';
|
||||
import {Stringify} from 'shared-runtime';
|
||||
|
||||
function Component({x, y, z}) {
|
||||
const a = useMemo(() => {
|
||||
return x?.y.z?.a;
|
||||
// error: too precise
|
||||
}, [x?.y.z?.a.b]);
|
||||
const b = useMemo(() => {
|
||||
return x.y.z?.a;
|
||||
// ok, not our job to type check nullability
|
||||
}, [x.y.z.a]);
|
||||
const c = useMemo(() => {
|
||||
return x?.y.z.a?.b;
|
||||
// error: too precise
|
||||
}, [x?.y.z.a?.b.z]);
|
||||
const d = useMemo(() => {
|
||||
return x?.y?.[(console.log(y), z?.b)];
|
||||
// ok
|
||||
}, [x?.y, y, z?.b]);
|
||||
const e = useMemo(() => {
|
||||
const e = [];
|
||||
e.push(x);
|
||||
return e;
|
||||
// ok
|
||||
}, [x]);
|
||||
const f = useMemo(() => {
|
||||
return [];
|
||||
// error: unnecessary
|
||||
}, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
const ref1 = useRef(null);
|
||||
const ref2 = useRef(null);
|
||||
const ref = z ? ref1 : ref2;
|
||||
const cb = useMemo(() => {
|
||||
return () => {
|
||||
return ref.current;
|
||||
};
|
||||
// error: ref is a stable type but reactive
|
||||
}, []);
|
||||
return <Stringify results={[a, b, c, d, e, f, cb]} />;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 5 errors:
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
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.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:7:11
|
||||
5 | function Component({x, y, z}) {
|
||||
6 | const a = useMemo(() => {
|
||||
> 7 | return x?.y.z?.a;
|
||||
| ^^^^^^^^^ Missing dependency `x?.y.z?.a`
|
||||
8 | // error: too precise
|
||||
9 | }, [x?.y.z?.a.b]);
|
||||
10 | const b = useMemo(() => {
|
||||
|
||||
error.invalid-exhaustive-deps.ts:9:6
|
||||
7 | return x?.y.z?.a;
|
||||
8 | // error: too precise
|
||||
> 9 | }, [x?.y.z?.a.b]);
|
||||
| ^^^^^^^^^^^ Overly precise dependency `x?.y.z?.a.b`, use `x?.y.z?.a` instead
|
||||
10 | const b = useMemo(() => {
|
||||
11 | return x.y.z?.a;
|
||||
12 | // ok, not our job to type check nullability
|
||||
|
||||
Inferred dependencies: `[x?.y.z?.a]`
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
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.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:15:11
|
||||
13 | }, [x.y.z.a]);
|
||||
14 | const c = useMemo(() => {
|
||||
> 15 | return x?.y.z.a?.b;
|
||||
| ^^^^^^^^^^^ Missing dependency `x?.y.z.a?.b`
|
||||
16 | // error: too precise
|
||||
17 | }, [x?.y.z.a?.b.z]);
|
||||
18 | const d = useMemo(() => {
|
||||
|
||||
error.invalid-exhaustive-deps.ts:17:6
|
||||
15 | return x?.y.z.a?.b;
|
||||
16 | // error: too precise
|
||||
> 17 | }, [x?.y.z.a?.b.z]);
|
||||
| ^^^^^^^^^^^^^ Overly precise dependency `x?.y.z.a?.b.z`, use `x?.y.z.a?.b` instead
|
||||
18 | const d = useMemo(() => {
|
||||
19 | return x?.y?.[(console.log(y), z?.b)];
|
||||
20 | // ok
|
||||
|
||||
Inferred dependencies: `[x?.y.z.a?.b]`
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
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.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:31:6
|
||||
29 | return [];
|
||||
30 | // error: unnecessary
|
||||
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
| ^ Unnecessary dependency `x`
|
||||
32 | const ref1 = useRef(null);
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
|
||||
error.invalid-exhaustive-deps.ts:31:9
|
||||
29 | return [];
|
||||
30 | // error: unnecessary
|
||||
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
| ^^^ Unnecessary dependency `y.z`
|
||||
32 | const ref1 = useRef(null);
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
|
||||
error.invalid-exhaustive-deps.ts:31:14
|
||||
29 | return [];
|
||||
30 | // error: unnecessary
|
||||
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
| ^^^^^^^ Unnecessary dependency `z?.y?.a`
|
||||
32 | const ref1 = useRef(null);
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
|
||||
error.invalid-exhaustive-deps.ts:31:23
|
||||
29 | return [];
|
||||
30 | // error: unnecessary
|
||||
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
| ^^^^^^^^^^^^^ 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
|
||||
32 | const ref1 = useRef(null);
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
|
||||
Inferred dependencies: `[]`
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
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.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:31:6
|
||||
29 | return [];
|
||||
30 | // error: unnecessary
|
||||
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
| ^ Unnecessary dependency `x`
|
||||
32 | const ref1 = useRef(null);
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
|
||||
error.invalid-exhaustive-deps.ts:31:9
|
||||
29 | return [];
|
||||
30 | // error: unnecessary
|
||||
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
| ^^^ Unnecessary dependency `y.z`
|
||||
32 | const ref1 = useRef(null);
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
|
||||
error.invalid-exhaustive-deps.ts:31:14
|
||||
29 | return [];
|
||||
30 | // error: unnecessary
|
||||
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
| ^^^^^^^ Unnecessary dependency `z?.y?.a`
|
||||
32 | const ref1 = useRef(null);
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
|
||||
error.invalid-exhaustive-deps.ts:31:23
|
||||
29 | return [];
|
||||
30 | // error: unnecessary
|
||||
> 31 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
|
||||
| ^^^^^^^^^^^^^ 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
|
||||
32 | const ref1 = useRef(null);
|
||||
33 | const ref2 = useRef(null);
|
||||
34 | const ref = z ? ref1 : ref2;
|
||||
|
||||
Inferred dependencies: `[]`
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.invalid-exhaustive-deps.ts:37:13
|
||||
35 | const cb = useMemo(() => {
|
||||
36 | return () => {
|
||||
> 37 | return ref.current;
|
||||
| ^^^ 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
|
||||
38 | };
|
||||
39 | // error: ref is a stable type but reactive
|
||||
40 | }, []);
|
||||
|
||||
Inferred dependencies: `[ref]`
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives} from 'shared-runtime';
|
||||
|
||||
function useHook() {
|
||||
const object = makeObject_Primitives();
|
||||
const fn = useCallback(() => {
|
||||
const g = () => {
|
||||
return [object];
|
||||
};
|
||||
return g;
|
||||
});
|
||||
return fn;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.invalid-missing-nonreactive-dep-inner-function.ts:10:14
|
||||
8 | const fn = useCallback(() => {
|
||||
9 | const g = () => {
|
||||
> 10 | return [object];
|
||||
| ^^^^^^ Missing dependency `object`
|
||||
11 | };
|
||||
12 | return g;
|
||||
13 | });
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives} from 'shared-runtime';
|
||||
|
||||
function useHook() {
|
||||
const object = makeObject_Primitives();
|
||||
const fn = useCallback(() => {
|
||||
const g = () => {
|
||||
return [object];
|
||||
};
|
||||
return g;
|
||||
});
|
||||
return fn;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, useIdentity} from 'shared-runtime';
|
||||
|
||||
function useHook() {
|
||||
// object is non-reactive but not memoized bc the mutation surrounds a hook
|
||||
const object = makeObject_Primitives();
|
||||
useIdentity();
|
||||
object.x = 0;
|
||||
const array = useMemo(() => [object], []);
|
||||
return array;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.invalid-missing-nonreactive-dep-unmemoized.ts:11:31
|
||||
9 | useIdentity();
|
||||
10 | object.x = 0;
|
||||
> 11 | const array = useMemo(() => [object], []);
|
||||
| ^^^^^^ Missing dependency `object`
|
||||
12 | return array;
|
||||
13 | }
|
||||
14 |
|
||||
|
||||
Inferred dependencies: `[object]`
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives, useIdentity} from 'shared-runtime';
|
||||
|
||||
function useHook() {
|
||||
// object is non-reactive but not memoized bc the mutation surrounds a hook
|
||||
const object = makeObject_Primitives();
|
||||
useIdentity();
|
||||
object.x = 0;
|
||||
const array = useMemo(() => [object], []);
|
||||
return array;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives} from 'shared-runtime';
|
||||
|
||||
function useHook() {
|
||||
const object = makeObject_Primitives();
|
||||
const array = useMemo(() => [object], []);
|
||||
return array;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.invalid-missing-nonreactive-dep.ts:8:31
|
||||
6 | function useHook() {
|
||||
7 | const object = makeObject_Primitives();
|
||||
> 8 | const array = useMemo(() => [object], []);
|
||||
| ^^^^^^ Missing dependency `object`
|
||||
9 | return array;
|
||||
10 | }
|
||||
11 |
|
||||
|
||||
Inferred dependencies: `[object]`
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// @validateExhaustiveMemoizationDependencies
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {makeObject_Primitives} from 'shared-runtime';
|
||||
|
||||
function useHook() {
|
||||
const object = makeObject_Primitives();
|
||||
const array = useMemo(() => [object], []);
|
||||
return array;
|
||||
}
|
||||
@@ -25,9 +25,9 @@ function Component() {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.sketchy-code-exhaustive-deps.ts:8:16
|
||||
6 | const foo = useCallback(
|
||||
@@ -37,6 +37,8 @@ error.sketchy-code-exhaustive-deps.ts:8:16
|
||||
9 | }, // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
10 | []
|
||||
11 | );
|
||||
|
||||
Inferred dependencies: `[item]`
|
||||
```
|
||||
|
||||
|
||||
@@ -32,9 +32,9 @@ function useFoo(input1) {
|
||||
```
|
||||
Found 1 error:
|
||||
|
||||
Error: Found missing memoization dependencies
|
||||
Error: Found missing/extra memoization dependencies
|
||||
|
||||
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
|
||||
Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
|
||||
|
||||
error.useMemo-unrelated-mutation-in-depslist.ts:18:14
|
||||
16 | const memoized = useMemo(() => {
|
||||
@@ -44,6 +44,8 @@ error.useMemo-unrelated-mutation-in-depslist.ts:18:14
|
||||
19 |
|
||||
20 | return [x, memoized];
|
||||
21 | }
|
||||
|
||||
Inferred dependencies: `[x, y]`
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user