@samitouri / QOS-React-2 / commits / ddcecbbebf

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 +};