diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts index 041d2fbf009..1c471930f62 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -9,6 +9,7 @@ import { Effect, ValueKind, ValueReason } from "./HIR"; import { BUILTIN_SHAPES, BuiltInArrayId, + BuiltInUseActionStateId, BuiltInUseEffectHookId, BuiltInUseInsertionEffectHookId, BuiltInUseLayoutEffectHookId, @@ -266,6 +267,18 @@ const REACT_APIS: Array<[string, BuiltInType]> = [ returnValueReason: ValueReason.State, }), ], + [ + "useActionState", + addHook(DEFAULT_SHAPES, { + positionalParams: [], + restParam: Effect.Freeze, + returnType: { kind: "Object", shapeId: BuiltInUseActionStateId }, + calleeEffect: Effect.Read, + hookKind: "useActionState", + returnValueKind: ValueKind.Frozen, + returnValueReason: ValueReason.State, + }), + ], [ "useReducer", addHook(DEFAULT_SHAPES, { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts index eb5ea56212f..b79414de031 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1543,6 +1543,18 @@ export function isSetStateType(id: Identifier): boolean { return id.type.kind === "Function" && id.type.shapeId === "BuiltInSetState"; } +export function isUseActionStateType(id: Identifier): boolean { + return ( + id.type.kind === "Object" && id.type.shapeId === "BuiltInUseActionState" + ); +} + +export function isSetActionStateType(id: Identifier): boolean { + return ( + id.type.kind === "Function" && id.type.shapeId === "BuiltInSetActionState" + ); +} + export function isUseReducerType(id: Identifier): boolean { return id.type.kind === "Function" && id.type.shapeId === "BuiltInUseReducer"; } @@ -1551,6 +1563,10 @@ export function isDispatcherType(id: Identifier): boolean { return id.type.kind === "Function" && id.type.shapeId === "BuiltInDispatch"; } +export function isStableType(id: Identifier): boolean { + return isSetStateType(id) || isSetActionStateType(id) || isDispatcherType(id); +} + export function isUseEffectHookType(id: Identifier): boolean { return ( id.type.kind === "Function" && id.type.shapeId === "BuiltInUseEffectHook" diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts index 296b3edb762..0a3451023cd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts @@ -118,6 +118,7 @@ function addShape( export type HookKind = | "useContext" | "useState" + | "useActionState" | "useReducer" | "useRef" | "useEffect" @@ -195,6 +196,8 @@ export const BuiltInJsxId = "BuiltInJsx"; export const BuiltInObjectId = "BuiltInObject"; export const BuiltInUseStateId = "BuiltInUseState"; export const BuiltInSetStateId = "BuiltInSetState"; +export const BuiltInUseActionStateId = "BuiltInUseActionState"; +export const BuiltInSetActionStateId = "BuiltInSetActionState"; export const BuiltInUseRefId = "BuiltInUseRefId"; export const BuiltInRefValueId = "BuiltInRefValue"; export const BuiltInMixedReadonlyId = "BuiltInMixedReadonly"; @@ -396,6 +399,25 @@ addObject(BUILTIN_SHAPES, BuiltInUseStateId, [ ], ]); +addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [ + ["0", { kind: "Poly" }], + [ + "1", + addFunction( + BUILTIN_SHAPES, + [], + { + positionalParams: [], + restParam: Effect.Freeze, + returnType: PRIMITIVE_TYPE, + calleeEffect: Effect.Read, + returnValueKind: ValueKind.Primitive, + }, + BuiltInSetActionStateId + ), + ], +]); + addObject(BUILTIN_SHAPES, BuiltInUseReducerId, [ ["0", { kind: "Poly" }], [ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts index e6a7bb49ce1..6d53c8f1574 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts @@ -15,8 +15,7 @@ import { Place, computePostDominatorTree, getHookKind, - isDispatcherType, - isSetStateType, + isStableType, isUseOperator, } from "../HIR"; import { PostDominator } from "../HIR/Dominator"; @@ -220,10 +219,7 @@ export function inferReactivePlaces(fn: HIRFunction): void { if (hasReactiveInput) { for (const lvalue of eachInstructionLValue(instruction)) { - if ( - isSetStateType(lvalue.identifier) || - isDispatcherType(lvalue.identifier) - ) { + if (isStableType(lvalue.identifier)) { continue; } reactiveIdentifiers.markReactive(lvalue); diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts index aef5d50ee3a..2e1748fc6a4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts @@ -10,8 +10,7 @@ import { ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, - isDispatcherType, - isSetStateType, + isStableType, } from "../HIR"; import { eachPatternOperand } from "../HIR/visitors"; import { collectReactiveIdentifiers } from "./CollectReactiveIdentifiers"; @@ -57,10 +56,7 @@ class Visitor extends ReactiveFunctionVisitor { case "Destructure": { if (state.has(value.value.identifier.id)) { for (const lvalue of eachPatternOperand(value.lvalue.pattern)) { - if ( - isSetStateType(lvalue.identifier) || - isDispatcherType(lvalue.identifier) - ) { + if (isStableType(lvalue.identifier)) { continue; } state.add(lvalue.identifier.id); @@ -75,7 +71,7 @@ class Visitor extends ReactiveFunctionVisitor { if ( lvalue !== null && state.has(value.object.identifier.id) && - !isSetStateType(lvalue.identifier) + !isStableType(lvalue.identifier) ) { state.add(lvalue.identifier.id); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md new file mode 100644 index 00000000000..edec816cb93 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md @@ -0,0 +1,57 @@ + +## Input + +```javascript +import { useActionState } from "react"; + +function Component() { + const [actionState, dispatchAction] = useActionState(); + const onSubmitAction = () => { + dispatchAction(); + }; + return ; +} + +function Foo() {} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useActionState } from "react"; + +function Component() { + const $ = _c(1); + const [actionState, dispatchAction] = useActionState(); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + const onSubmitAction = () => { + dispatchAction(); + }; + + t0 = ; + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +function Foo() {} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +### Eval output +(kind: ok) \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.js new file mode 100644 index 00000000000..04bf7eaeb2e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.js @@ -0,0 +1,16 @@ +import { useActionState } from "react"; + +function Component() { + const [actionState, dispatchAction] = useActionState(); + const onSubmitAction = () => { + dispatchAction(); + }; + return ; +} + +function Foo() {} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +};