Skip to content

Commit ea242e5

Browse files
committed
Handle useEffectEvent - should never be included in effect deps
1 parent 0a93bf8 commit ea242e5

File tree

7 files changed

+321
-6
lines changed

7 files changed

+321
-6
lines changed

compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateExhaustiveDependencies.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
Identifier,
2626
IdentifierId,
2727
InstructionKind,
28+
isEffectEventFunctionType,
2829
isPrimitiveType,
2930
isStableType,
3031
isSubPath,
@@ -317,6 +318,12 @@ function validateDependencies(
317318
reason: 'Unexpected function dependency',
318319
loc: inferredDependency.loc,
319320
});
321+
/**
322+
* Skip effect event functions as they are not valid dependencies
323+
*/
324+
if (isEffectEventFunctionType(inferredDependency.identifier)) {
325+
continue;
326+
}
320327
let hasMatchingManualDependency = false;
321328
for (const manualDependency of manualDependencies) {
322329
if (
@@ -339,6 +346,7 @@ function validateDependencies(
339346
) {
340347
continue;
341348
}
349+
342350
missing.push(inferredDependency);
343351
}
344352

@@ -416,6 +424,17 @@ function validateDependencies(
416424
},
417425
);
418426
if (
427+
matchingInferred != null &&
428+
isEffectEventFunctionType(matchingInferred.identifier)
429+
) {
430+
diagnostic.withDetails({
431+
kind: 'error',
432+
message:
433+
`Functions returned from \`useEffectEvent\` must not be included in the dependency array. ` +
434+
`Remove \`${printManualMemoDependency(dep)}\` from the dependencies.`,
435+
loc: dep.loc ?? manualMemoLoc,
436+
});
437+
} else if (
419438
matchingInferred != null &&
420439
!isOptionalDependency(matchingInferred, reactive)
421440
) {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
2+
## Input
3+
4+
```javascript
5+
// @validateExhaustiveEffectDependencies
6+
import {useEffect, useEffectEvent} from 'react';
7+
8+
function Component({x, y, z}) {
9+
const effectEvent = useEffectEvent(() => {
10+
log(x);
11+
});
12+
13+
const effectEvent2 = useEffectEvent(z => {
14+
log(y, z);
15+
});
16+
17+
// error - do not include effect event in deps
18+
useEffect(() => {
19+
effectEvent();
20+
}, [effectEvent]);
21+
22+
// error - do not include effect event in deps
23+
useEffect(() => {
24+
effectEvent2(z);
25+
}, [effectEvent2, z]);
26+
27+
// error - do not include effect event captured values in deps
28+
useEffect(() => {
29+
effectEvent2(z);
30+
}, [y, z]);
31+
}
32+
33+
```
34+
35+
36+
## Error
37+
38+
```
39+
Found 3 errors:
40+
41+
Error: Found extra effect dependencies
42+
43+
Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.
44+
45+
error.exhaustive-deps-effect-events.ts:16:6
46+
14 | useEffect(() => {
47+
15 | effectEvent();
48+
> 16 | }, [effectEvent]);
49+
| ^^^^^^^^^^^ Functions returned from `useEffectEvent` must not be included in the dependency array. Remove `effectEvent` from the dependencies.
50+
17 |
51+
18 | // error - do not include effect event in deps
52+
19 | useEffect(() => {
53+
54+
Error: Found extra effect dependencies
55+
56+
Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.
57+
58+
error.exhaustive-deps-effect-events.ts:21:6
59+
19 | useEffect(() => {
60+
20 | effectEvent2(z);
61+
> 21 | }, [effectEvent2, z]);
62+
| ^^^^^^^^^^^^ Functions returned from `useEffectEvent` must not be included in the dependency array. Remove `effectEvent2` from the dependencies.
63+
22 |
64+
23 | // error - do not include effect event captured values in deps
65+
24 | useEffect(() => {
66+
67+
Error: Found extra effect dependencies
68+
69+
Extra dependencies can cause an effect to fire more often than it should, resulting in performance problems such as excessive renders and side effects.
70+
71+
error.exhaustive-deps-effect-events.ts:26:6
72+
24 | useEffect(() => {
73+
25 | effectEvent2(z);
74+
> 26 | }, [y, z]);
75+
| ^ Unnecessary dependency `y`
76+
27 | }
77+
28 |
78+
```
79+
80+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// @validateExhaustiveEffectDependencies
2+
import {useEffect, useEffectEvent} from 'react';
3+
4+
function Component({x, y, z}) {
5+
const effectEvent = useEffectEvent(() => {
6+
log(x);
7+
});
8+
9+
const effectEvent2 = useEffectEvent(z => {
10+
log(y, z);
11+
});
12+
13+
// error - do not include effect event in deps
14+
useEffect(() => {
15+
effectEvent();
16+
}, [effectEvent]);
17+
18+
// error - do not include effect event in deps
19+
useEffect(() => {
20+
effectEvent2(z);
21+
}, [effectEvent2, z]);
22+
23+
// error - do not include effect event captured values in deps
24+
useEffect(() => {
25+
effectEvent2(z);
26+
}, [y, z]);
27+
}

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/exhaustive-deps-allow-nonreactive-stable-types-as-extra-deps.expect.md

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
## Input
33

44
```javascript
5-
// @validateExhaustiveMemoizationDependencies
5+
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
66
import {
77
useCallback,
88
useTransition,
@@ -21,6 +21,24 @@ function useFoo() {
2121
const [v, dispatch] = useReducer(() => {}, null);
2222
const [isPending, dispatchAction] = useActionState(() => {}, null);
2323

24+
useEffect(() => {
25+
dispatch();
26+
startTransition(() => {});
27+
addOptimistic();
28+
setState(null);
29+
dispatchAction();
30+
ref.current = true;
31+
}, [
32+
// intentionally adding unnecessary deps on nonreactive stable values
33+
// to check that they're allowed
34+
dispatch,
35+
startTransition,
36+
addOptimistic,
37+
setState,
38+
dispatchAction,
39+
ref,
40+
]);
41+
2442
return useCallback(() => {
2543
dispatch();
2644
startTransition(() => {});
@@ -50,7 +68,7 @@ export const FIXTURE_ENTRYPOINT = {
5068
## Code
5169

5270
```javascript
53-
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies
71+
import { c as _c } from "react/compiler-runtime"; // @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
5472
import {
5573
useCallback,
5674
useTransition,
@@ -62,14 +80,15 @@ import {
6280
} from "react";
6381

6482
function useFoo() {
65-
const $ = _c(1);
83+
const $ = _c(3);
6684
const [, setState] = useState();
6785
const ref = useRef(null);
6886
const [, startTransition] = useTransition();
6987
const [, addOptimistic] = useOptimistic();
7088
const [, dispatch] = useReducer(_temp, null);
7189
const [, dispatchAction] = useActionState(_temp2, null);
7290
let t0;
91+
let t1;
7392
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
7493
t0 = () => {
7594
dispatch();
@@ -79,12 +98,38 @@ function useFoo() {
7998
dispatchAction();
8099
ref.current = true;
81100
};
101+
t1 = [
102+
dispatch,
103+
startTransition,
104+
addOptimistic,
105+
setState,
106+
dispatchAction,
107+
ref,
108+
];
82109
$[0] = t0;
110+
$[1] = t1;
83111
} else {
84112
t0 = $[0];
113+
t1 = $[1];
114+
}
115+
useEffect(t0, t1);
116+
let t2;
117+
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
118+
t2 = () => {
119+
dispatch();
120+
startTransition(_temp4);
121+
addOptimistic();
122+
setState(null);
123+
dispatchAction();
124+
ref.current = true;
125+
};
126+
$[2] = t2;
127+
} else {
128+
t2 = $[2];
85129
}
86-
return t0;
130+
return t2;
87131
}
132+
function _temp4() {}
88133
function _temp3() {}
89134
function _temp2() {}
90135
function _temp() {}
@@ -97,4 +142,4 @@ export const FIXTURE_ENTRYPOINT = {
97142
```
98143
99144
### Eval output
100-
(kind: ok) "[[ function params=0 ]]"
145+
(kind: exception) useEffect is not defined

compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/exhaustive-deps-allow-nonreactive-stable-types-as-extra-deps.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// @validateExhaustiveMemoizationDependencies
1+
// @validateExhaustiveMemoizationDependencies @validateExhaustiveEffectDependencies
22
import {
33
useCallback,
44
useTransition,
@@ -17,6 +17,24 @@ function useFoo() {
1717
const [v, dispatch] = useReducer(() => {}, null);
1818
const [isPending, dispatchAction] = useActionState(() => {}, null);
1919

20+
useEffect(() => {
21+
dispatch();
22+
startTransition(() => {});
23+
addOptimistic();
24+
setState(null);
25+
dispatchAction();
26+
ref.current = true;
27+
}, [
28+
// intentionally adding unnecessary deps on nonreactive stable values
29+
// to check that they're allowed
30+
dispatch,
31+
startTransition,
32+
addOptimistic,
33+
setState,
34+
dispatchAction,
35+
ref,
36+
]);
37+
2038
return useCallback(() => {
2139
dispatch();
2240
startTransition(() => {});

0 commit comments

Comments
 (0)