Consider dispatch function from useActionState non-reactive (#29917)
Updated version of #29758 removing `useFormState` since that was the previous name for `useActionState`. --------- Co-authored-by: Hieu Do <hieudn.uh@gmail.com>
Joseph Savona committed
Jun 17, 2024 at 12:26 UTC
ddcecbbebf6af3fa32a323c0591dad1f63587aeb
7 files changed
+129
-13
compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts
+13
@@ -9,6 +9,7 @@ import { Effect, ValueKind, ValueReason } from "./HIR";
9
import {
10
BUILTIN_SHAPES,
11
BuiltInArrayId,
12
+ BuiltInUseActionStateId,
13
BuiltInUseEffectHookId,
14
BuiltInUseInsertionEffectHookId,
15
BuiltInUseLayoutEffectHookId,
@@ -266,6 +267,18 @@ const REACT_APIS: Array<[string, BuiltInType]> = [
267
returnValueReason: ValueReason.State,
268
}),
269
],
270
+ [
271
+ "useActionState",
272
+ addHook(DEFAULT_SHAPES, {
273
+ positionalParams: [],
274
+ restParam: Effect.Freeze,
275
+ returnType: { kind: "Object", shapeId: BuiltInUseActionStateId },
276
+ calleeEffect: Effect.Read,
277
+ hookKind: "useActionState",
278
+ returnValueKind: ValueKind.Frozen,
279
+ returnValueReason: ValueReason.State,
280
+ }),
281
+ ],
282
[
283
"useReducer",
284
addHook(DEFAULT_SHAPES, {
compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts
+16
@@ -1543,6 +1543,18 @@ export function isSetStateType(id: Identifier): boolean {
1543
return id.type.kind === "Function" && id.type.shapeId === "BuiltInSetState";
1544
}
1545
1546
+export function isUseActionStateType(id: Identifier): boolean {
1547
+ return (
1548
+ id.type.kind === "Object" && id.type.shapeId === "BuiltInUseActionState"
1549
+ );
1550
+}
1551
+
1552
+export function isSetActionStateType(id: Identifier): boolean {
1553
+ return (
1554
+ id.type.kind === "Function" && id.type.shapeId === "BuiltInSetActionState"
1555
+ );
1556
+}
1557
+
1558
export function isUseReducerType(id: Identifier): boolean {
1559
return id.type.kind === "Function" && id.type.shapeId === "BuiltInUseReducer";
1560
}
@@ -1551,6 +1563,10 @@ export function isDispatcherType(id: Identifier): boolean {
1563
return id.type.kind === "Function" && id.type.shapeId === "BuiltInDispatch";
1564
}
1565
1566
+export function isStableType(id: Identifier): boolean {
1567
+ return isSetStateType(id) || isSetActionStateType(id) || isDispatcherType(id);
1568
+}
1569
+
1570
export function isUseEffectHookType(id: Identifier): boolean {
1571
return (
1572
id.type.kind === "Function" && id.type.shapeId === "BuiltInUseEffectHook"
compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts
+22
@@ -118,6 +118,7 @@ function addShape(
118
export type HookKind =
119
| "useContext"
120
| "useState"
121
+ | "useActionState"
122
| "useReducer"
123
| "useRef"
124
| "useEffect"
@@ -195,6 +196,8 @@ export const BuiltInJsxId = "BuiltInJsx";
196
export const BuiltInObjectId = "BuiltInObject";
197
export const BuiltInUseStateId = "BuiltInUseState";
198
export const BuiltInSetStateId = "BuiltInSetState";
199
+export const BuiltInUseActionStateId = "BuiltInUseActionState";
200
+export const BuiltInSetActionStateId = "BuiltInSetActionState";
201
export const BuiltInUseRefId = "BuiltInUseRefId";
202
export const BuiltInRefValueId = "BuiltInRefValue";
203
export const BuiltInMixedReadonlyId = "BuiltInMixedReadonly";
@@ -396,6 +399,25 @@ addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
399
],
400
]);
401
402
+addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [
403
+ ["0", { kind: "Poly" }],
404
+ [
405
+ "1",
406
+ addFunction(
407
+ BUILTIN_SHAPES,
408
+ [],
409
+ {
410
+ positionalParams: [],
411
+ restParam: Effect.Freeze,
412
+ returnType: PRIMITIVE_TYPE,
413
+ calleeEffect: Effect.Read,
414
+ returnValueKind: ValueKind.Primitive,
415
+ },
416
+ BuiltInSetActionStateId
417
+ ),
418
+ ],
419
+]);
420
+
421
addObject(BUILTIN_SHAPES, BuiltInUseReducerId, [
422
["0", { kind: "Poly" }],
423
[
compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts
+2
-6
@@ -15,8 +15,7 @@ import {
15
Place,
16
computePostDominatorTree,
17
getHookKind,
18
- isDispatcherType,
19
- isSetStateType,
18
+ isStableType,
19
isUseOperator,
20
} from "../HIR";
21
import { PostDominator } from "../HIR/Dominator";
@@ -220,10 +219,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
219
220
if (hasReactiveInput) {
221
for (const lvalue of eachInstructionLValue(instruction)) {
223
- if (
224
- isSetStateType(lvalue.identifier) ||
225
- isDispatcherType(lvalue.identifier)
226
- ) {
222
+ if (isStableType(lvalue.identifier)) {
223
continue;
224
}
225
reactiveIdentifiers.markReactive(lvalue);
compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts
+3
-7
@@ -10,8 +10,7 @@ import {
10
ReactiveFunction,
11
ReactiveInstruction,
12
ReactiveScopeBlock,
13
- isDispatcherType,
14
- isSetStateType,
13
+ isStableType,
14
} from "../HIR";
15
import { eachPatternOperand } from "../HIR/visitors";
16
import { collectReactiveIdentifiers } from "./CollectReactiveIdentifiers";
@@ -57,10 +56,7 @@ class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
56
case "Destructure": {
57
if (state.has(value.value.identifier.id)) {
58
for (const lvalue of eachPatternOperand(value.lvalue.pattern)) {
60
- if (
61
- isSetStateType(lvalue.identifier) ||
62
- isDispatcherType(lvalue.identifier)
63
- ) {
59
+ if (isStableType(lvalue.identifier)) {
60
continue;
61
}
62
state.add(lvalue.identifier.id);
@@ -75,7 +71,7 @@ class Visitor extends ReactiveFunctionVisitor<ReactiveIdentifiers> {
71
if (
72
lvalue !== null &&
73
state.has(value.object.identifier.id) &&
78
- !isSetStateType(lvalue.identifier)
74
+ !isStableType(lvalue.identifier)
75
) {
76
state.add(lvalue.identifier.id);
77
}
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md
new
+57
@@ -0,0 +1,57 @@
1
+
2
+## Input
3
+
4
+```javascript
5
+import { useActionState } from "react";
6
+
7
+function Component() {
8
+ const [actionState, dispatchAction] = useActionState();
9
+ const onSubmitAction = () => {
10
+ dispatchAction();
11
+ };
12
+ return <Foo onSubmitAction={onSubmitAction} />;
13
+}
14
+
15
+function Foo() {}
16
+
17
+export const FIXTURE_ENTRYPOINT = {
18
+ fn: Component,
19
+ params: [],
20
+};
21
+
22
+```
23
+
24
+## Code
25
+
26
+```javascript
27
+import { c as _c } from "react/compiler-runtime";
28
+import { useActionState } from "react";
29
+
30
+function Component() {
31
+ const $ = _c(1);
32
+ const [actionState, dispatchAction] = useActionState();
33
+ let t0;
34
+ if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
35
+ const onSubmitAction = () => {
36
+ dispatchAction();
37
+ };
38
+
39
+ t0 = <Foo onSubmitAction={onSubmitAction} />;
40
+ $[0] = t0;
41
+ } else {
42
+ t0 = $[0];
43
+ }
44
+ return t0;
45
+}
46
+
47
+function Foo() {}
48
+
49
+export const FIXTURE_ENTRYPOINT = {
50
+ fn: Component,
51
+ params: [],
52
+};
53
+
54
+```
55
+
56
+### Eval output
57
+(kind: ok)
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.js
new
+16
@@ -0,0 +1,16 @@
1
+import { useActionState } from "react";
2
+
3
+function Component() {
4
+ const [actionState, dispatchAction] = useActionState();
5
+ const onSubmitAction = () => {
6
+ dispatchAction();
7
+ };
8
+ return <Foo onSubmitAction={onSubmitAction} />;
9
+}
10
+
11
+function Foo() {}
12
+
13
+export const FIXTURE_ENTRYPOINT = {
14
+ fn: Component,
15
+ params: [],
16
+};