diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts
index df85ea8648c..01c82a375c9 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts
+++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts
@@ -24,6 +24,7 @@ import {
InstructionKind,
isStableType,
isSubPath,
+ isSubPathIgnoringOptionals,
isUseRefType,
LoadGlobal,
ManualMemoDependency,
@@ -240,7 +241,10 @@ export function validateExhaustiveDependencies(
manualDependency.root.value.identifier.id ===
inferredDependency.identifier.id &&
(areEqualPaths(manualDependency.path, inferredDependency.path) ||
- isSubPath(manualDependency.path, inferredDependency.path))
+ isSubPathIgnoringOptionals(
+ manualDependency.path,
+ inferredDependency.path,
+ ))
) {
hasMatchingManualDependency = true;
matched.add(manualDependency);
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-exhaustive-deps.expect.md
index 5152f4121a2..bca95a84e1e 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-exhaustive-deps.expect.md
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-exhaustive-deps.expect.md
@@ -9,23 +9,29 @@ 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);
@@ -34,6 +40,7 @@ function Component({x, y, z}) {
return () => {
return ref.current;
};
+ // error: ref is a stable type but reactive
}, []);
return ;
}
@@ -44,7 +51,7 @@ function Component({x, y, z}) {
## Error
```
-Found 5 errors:
+Found 4 errors:
Error: Found non-exhaustive dependencies
@@ -55,61 +62,48 @@ error.invalid-exhaustive-deps.ts:7:11
6 | const a = useMemo(() => {
> 7 | return x?.y.z?.a;
| ^^^^^^^^^ Missing dependency `x?.y.z?.a`
- 8 | }, [x?.y.z?.a.b]);
- 9 | const b = useMemo(() => {
- 10 | return x.y.z?.a;
+ 8 | // error: too precise
+ 9 | }, [x?.y.z?.a.b]);
+ 10 | const b = useMemo(() => {
Error: Found non-exhaustive dependencies
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
-error.invalid-exhaustive-deps.ts:10:11
- 8 | }, [x?.y.z?.a.b]);
- 9 | const b = useMemo(() => {
-> 10 | return x.y.z?.a;
- | ^^^^^^^^ Missing dependency `x.y.z?.a`
- 11 | }, [x.y.z.a]);
- 12 | const c = useMemo(() => {
- 13 | return x?.y.z.a?.b;
-
-Error: Found non-exhaustive dependencies
-
-Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
-
-error.invalid-exhaustive-deps.ts:13:11
- 11 | }, [x.y.z.a]);
- 12 | const c = useMemo(() => {
-> 13 | return x?.y.z.a?.b;
+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`
- 14 | }, [x?.y.z.a?.b.z]);
- 15 | const d = useMemo(() => {
- 16 | return x?.y?.[(console.log(y), z?.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, which can cause effects to run more than expected.
-error.invalid-exhaustive-deps.ts:25:5
- 23 | const f = useMemo(() => {
- 24 | return [];
-> 25 | }, [x, y.z, z?.y?.a, UNUSED_GLOBAL]);
+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`
- 26 | const ref1 = useRef(null);
- 27 | const ref2 = useRef(null);
- 28 | const ref = z ? ref1 : ref2;
+ 32 | const ref1 = useRef(null);
+ 33 | const ref2 = useRef(null);
+ 34 | const ref = z ? ref1 : ref2;
Error: Found non-exhaustive dependencies
Missing dependencies can cause a value not to update when those inputs change, resulting in stale UI.
-error.invalid-exhaustive-deps.ts:31:13
- 29 | const cb = useMemo(() => {
- 30 | return () => {
-> 31 | return ref.current;
+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
- 32 | };
- 33 | }, []);
- 34 | return ;
+ 38 | };
+ 39 | // error: ref is a stable type but reactive
+ 40 | }, []);
```
\ No newline at end of file
diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-exhaustive-deps.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-exhaustive-deps.js
index 53c189cc428..c0f8d28837a 100644
--- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-exhaustive-deps.js
+++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-exhaustive-deps.js
@@ -5,23 +5,29 @@ 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);
@@ -30,6 +36,7 @@ function Component({x, y, z}) {
return () => {
return ref.current;
};
+ // error: ref is a stable type but reactive
}, []);
return ;
}