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 931d315d308e0..c868b92077530 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -9,7 +9,9 @@ import { Effect, ValueKind, ValueReason } from "./HIR"; import { BUILTIN_SHAPES, BuiltInArrayId, + BuiltInUseActionStateId, BuiltInUseEffectHookId, + BuiltInUseFormStateId, BuiltInUseInsertionEffectHookId, BuiltInUseLayoutEffectHookId, BuiltInUseOperatorId, @@ -265,6 +267,30 @@ 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, + }), + ], + [ + "useFormState", + addHook(DEFAULT_SHAPES, { + positionalParams: [], + restParam: Effect.Freeze, + returnType: { kind: "Object", shapeId: BuiltInUseFormStateId }, + calleeEffect: Effect.Read, + hookKind: "useFormState", + returnValueKind: ValueKind.Frozen, + returnValueReason: ValueReason.State, + }), + ], [ "useRef", 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 da900c275ca7e..cac53b297b80b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1490,6 +1490,26 @@ 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 isUseFormStateType(id: Identifier): boolean { + return id.type.kind === "Object" && id.type.shapeId === 'BuiltInUseFormState' +} + +export function isSetFormStateType(id: Identifier): boolean { + return id.type.kind === "Function" && id.type.shapeId === "BuiltInSetFormState"; +} + +export function isDispatchType(id: Identifier): boolean { + return isSetActionStateType(id)|| isSetFormStateType(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 fd04bf43c2950..d656549626ac0 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,8 @@ function addShape( export type HookKind = | "useContext" | "useState" + | "useActionState" + | "useFormState" | "useRef" | "useEffect" | "useLayoutEffect" @@ -193,6 +195,10 @@ 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 BuiltInUseFormStateId = "BuiltInUseFormState"; +export const BuiltInSetFormStateId = "BuiltInSetFormState"; export const BuiltInUseRefId = "BuiltInUseRefId"; export const BuiltInRefValueId = "BuiltInRefValue"; export const BuiltInMixedReadonlyId = "BuiltInMixedReadonly"; @@ -387,6 +393,44 @@ 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, BuiltInUseFormStateId, [ + ["0", { kind: "Poly" }], + [ + "1", + addFunction( + BUILTIN_SHAPES, + [], + { + positionalParams: [], + restParam: Effect.Freeze, + returnType: PRIMITIVE_TYPE, + calleeEffect: Effect.Read, + returnValueKind: ValueKind.Primitive, + }, + BuiltInSetFormStateId + ), + ], +]); + addObject(BUILTIN_SHAPES, BuiltInUseRefId, [ ["current", { kind: "Object", shapeId: BuiltInRefValueId }], ]); 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 ad2f666ac16d1..e136fb81553d3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferReactivePlaces.ts @@ -15,6 +15,7 @@ import { Place, computePostDominatorTree, getHookKind, + isDispatchType, isSetStateType, isUseOperator, } from "../HIR"; @@ -219,7 +220,10 @@ export function inferReactivePlaces(fn: HIRFunction): void { if (hasReactiveInput) { for (const lvalue of eachInstructionLValue(instruction)) { - if (isSetStateType(lvalue.identifier)) { + if ( + isSetStateType(lvalue.identifier) || + isDispatchType(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 0c82cefc59f06..019e33016c087 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneNonReactiveDependencies.ts @@ -10,6 +10,7 @@ import { ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, + isDispatchType, isSetStateType, } from "../HIR"; import { eachPatternOperand } from "../HIR/visitors"; @@ -56,7 +57,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)) { + if (isSetStateType(lvalue.identifier) || isDispatchType(lvalue.identifier)) { continue; } state.add(lvalue.identifier.id); @@ -71,7 +72,7 @@ class Visitor extends ReactiveFunctionVisitor { if ( lvalue !== null && state.has(value.object.identifier.id) && - !isSetStateType(lvalue.identifier) + !isSetStateType(lvalue.identifier) && !isDispatchType(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 0000000000000..c0366e605d7ea --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md @@ -0,0 +1,63 @@ + +## 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(2); + const [actionState, dispatchAction] = useActionState(); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = () => { + dispatchAction(); + }; + $[0] = t0; + } else { + t0 = $[0]; + } + const onSubmitAction = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = ; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +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 0000000000000..04bf7eaeb2e51 --- /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: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.expect.md new file mode 100644 index 0000000000000..bd6ad05eeb568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.expect.md @@ -0,0 +1,63 @@ + +## Input + +```javascript +import { useFormState } from "react-dom"; + +function Component() { + const [formState, dispatchForm] = useFormState(); + const onSubmitForm = () => { + dispatchForm(); + }; + return ; +} + +function Foo() {} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { useFormState } from "react-dom"; + +function Component() { + const $ = _c(2); + const [formState, dispatchForm] = useFormState(); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = () => { + dispatchForm(); + }; + $[0] = t0; + } else { + t0 = $[0]; + } + const onSubmitForm = t0; + let t1; + if ($[1] === Symbol.for("react.memo_cache_sentinel")) { + t1 = ; + $[1] = t1; + } else { + t1 = $[1]; + } + return t1; +} + +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/useFormState-dispatch-considered-as-non-reactive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.js new file mode 100644 index 0000000000000..f58903e7a54a3 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.js @@ -0,0 +1,16 @@ +import { useFormState } from "react-dom"; + +function Component() { + const [formState, dispatchForm] = useFormState(); + const onSubmitForm = () => { + dispatchForm(); + }; + return ; +} + +function Foo() {} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [], +};