From 62badabb131483492eadbeb3e2480de11e1bd4f6 Mon Sep 17 00:00:00 2001 From: Hieu Do Date: Wed, 5 Jun 2024 21:36:31 +0700 Subject: [PATCH 1/2] feat(compiler): Consider dispatch fn from useActionState and useFormState to be non-reactive --- .../src/HIR/Globals.ts | 26 ++++++++ .../src/HIR/HIR.ts | 31 +++++++++ .../src/HIR/ObjectShape.ts | 44 +++++++++++++ .../src/Inference/InferReactivePlaces.ts | 8 +-- .../PruneNonReactiveDependencies.ts | 10 +-- ...patch-considered-as-non-reactive.expect.md | 63 +++++++++++++++++++ ...ate-dispatch-considered-as-non-reactive.js | 16 +++++ ...patch-considered-as-non-reactive.expect.md | 63 +++++++++++++++++++ ...ate-dispatch-considered-as-non-reactive.js | 16 +++++ 9 files changed, 264 insertions(+), 13 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useActionState-dispatch-considered-as-non-reactive.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.js 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..6a195ca6fd0 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, @@ -266,6 +268,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, + }), + ], [ "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..0460af7561a 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,28 @@ 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 isUseReducerType(id: Identifier): boolean { return id.type.kind === "Function" && id.type.shapeId === "BuiltInUseReducer"; } @@ -1551,6 +1573,15 @@ 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) || + isSetFormStateType(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..a0c8f74043e 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" | "useReducer" | "useRef" | "useEffect" @@ -195,6 +197,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"; @@ -396,6 +402,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, 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..c0366e605d7 --- /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 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: [], +}; 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 00000000000..bd6ad05eeb5 --- /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 00000000000..f58903e7a54 --- /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: [], +}; From c6f3f7aa864fcf795fa59002401268c75f199d9c Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Mon, 17 Jun 2024 08:27:03 -0700 Subject: [PATCH 2/2] Update name to `useFormStatus` --- .../src/HIR/Globals.ts | 13 ---- .../src/HIR/HIR.ts | 17 +---- .../src/HIR/ObjectShape.ts | 22 ------- ...patch-considered-as-non-reactive.expect.md | 16 ++--- ...patch-considered-as-non-reactive.expect.md | 63 ------------------- ...ate-dispatch-considered-as-non-reactive.js | 16 ----- 6 files changed, 6 insertions(+), 141 deletions(-) delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.expect.md delete mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.js 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 6a195ca6fd0..1c471930f62 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Globals.ts @@ -11,7 +11,6 @@ import { BuiltInArrayId, BuiltInUseActionStateId, BuiltInUseEffectHookId, - BuiltInUseFormStateId, BuiltInUseInsertionEffectHookId, BuiltInUseLayoutEffectHookId, BuiltInUseOperatorId, @@ -280,18 +279,6 @@ const REACT_APIS: Array<[string, BuiltInType]> = [ 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, - }), - ], [ "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 0460af7561a..b79414de031 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIR.ts @@ -1555,16 +1555,6 @@ export function isSetActionStateType(id: Identifier): boolean { ); } -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 isUseReducerType(id: Identifier): boolean { return id.type.kind === "Function" && id.type.shapeId === "BuiltInUseReducer"; } @@ -1574,12 +1564,7 @@ export function isDispatcherType(id: Identifier): boolean { } export function isStableType(id: Identifier): boolean { - return ( - isSetStateType(id) || - isSetActionStateType(id) || - isSetFormStateType(id) || - isDispatcherType(id) - ); + return isSetStateType(id) || isSetActionStateType(id) || isDispatcherType(id); } export function isUseEffectHookType(id: Identifier): boolean { 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 a0c8f74043e..0a3451023cd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/ObjectShape.ts @@ -119,7 +119,6 @@ export type HookKind = | "useContext" | "useState" | "useActionState" - | "useFormState" | "useReducer" | "useRef" | "useEffect" @@ -199,8 +198,6 @@ 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"; @@ -421,25 +418,6 @@ addObject(BUILTIN_SHAPES, BuiltInUseActionStateId, [ ], ]); -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, BuiltInUseReducerId, [ ["0", { kind: "Poly" }], [ 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 index c0366e605d7..edec816cb93 100644 --- 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 @@ -28,26 +28,20 @@ import { c as _c } from "react/compiler-runtime"; import { useActionState } from "react"; function Component() { - const $ = _c(2); + const $ = _c(1); const [actionState, dispatchAction] = useActionState(); let t0; if ($[0] === Symbol.for("react.memo_cache_sentinel")) { - t0 = () => { + const onSubmitAction = () => { dispatchAction(); }; + + t0 = ; $[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; + return t0; } function Foo() {} 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 deleted file mode 100644 index bd6ad05eeb5..00000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.expect.md +++ /dev/null @@ -1,63 +0,0 @@ - -## 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 deleted file mode 100644 index f58903e7a54..00000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/useFormState-dispatch-considered-as-non-reactive.js +++ /dev/null @@ -1,16 +0,0 @@ -import { useFormState } from "react-dom"; - -function Component() { - const [formState, dispatchForm] = useFormState(); - const onSubmitForm = () => { - dispatchForm(); - }; - return ; -} - -function Foo() {} - -export const FIXTURE_ENTRYPOINT = { - fn: Component, - params: [], -};