s/form state/action state (#28631)

Rename internals from "form state" to "action state"
This commit is contained in:
Ricky 2024-03-28 11:34:24 -04:00 committed by GitHub
parent 299a9c0598
commit 05797ccebd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 116 additions and 113 deletions

View File

@ -6187,8 +6187,8 @@ describe('ReactDOMFizzServer', () => {
// Because of the render phase update above, this component is evaluated
// multiple times (even during SSR), but it should only emit a single
// marker per useActionState instance.
const [formState] = useActionState(action, 0);
const text = `${readText('Child')}:${formState}:${localState}`;
const [actionState] = useActionState(action, 0);
const text = `${readText('Child')}:${actionState}:${localState}`;
return (
<div id="child" ref={childRef}>
{text}

View File

@ -1280,7 +1280,7 @@ describe('ReactDOMForm', () => {
});
// @gate enableAsyncActions
test('useFormState works in StrictMode', async () => {
test('useActionState works in StrictMode', async () => {
let actionCounter = 0;
async function action(state, type) {
actionCounter++;

View File

@ -1889,9 +1889,9 @@ function rerenderOptimistic<S, A>(
return [passthrough, dispatch];
}
// useFormState actions run sequentially, because each action receives the
// useActionState actions run sequentially, because each action receives the
// previous state as an argument. We store pending actions on a queue.
type FormStateActionQueue<S, P> = {
type ActionStateQueue<S, P> = {
// This is the most recent state returned from an action. It's updated as
// soon as the action finishes running.
state: Awaited<S>,
@ -1902,18 +1902,18 @@ type FormStateActionQueue<S, P> = {
action: (Awaited<S>, P) => S,
// This is a circular linked list of pending action payloads. It incudes the
// action that is currently running.
pending: FormStateActionQueueNode<P> | null,
pending: ActionStateQueueNode<P> | null,
};
type FormStateActionQueueNode<P> = {
type ActionStateQueueNode<P> = {
payload: P,
// This is never null because it's part of a circular linked list.
next: FormStateActionQueueNode<P>,
next: ActionStateQueueNode<P>,
};
function dispatchFormState<S, P>(
function dispatchActionState<S, P>(
fiber: Fiber,
actionQueue: FormStateActionQueue<S, P>,
actionQueue: ActionStateQueue<S, P>,
setPendingState: boolean => void,
setState: Dispatch<S | Awaited<S>>,
payload: P,
@ -1925,13 +1925,13 @@ function dispatchFormState<S, P>(
if (last === null) {
// There are no pending actions; this is the first one. We can run
// it immediately.
const newLast: FormStateActionQueueNode<P> = {
const newLast: ActionStateQueueNode<P> = {
payload,
next: (null: any), // circular
};
newLast.next = actionQueue.pending = newLast;
runFormStateAction(
runActionStateAction(
actionQueue,
(setPendingState: any),
(setState: any),
@ -1940,7 +1940,7 @@ function dispatchFormState<S, P>(
} else {
// There's already an action running. Add to the queue.
const first = last.next;
const newLast: FormStateActionQueueNode<P> = {
const newLast: ActionStateQueueNode<P> = {
payload,
next: first,
};
@ -1948,8 +1948,8 @@ function dispatchFormState<S, P>(
}
}
function runFormStateAction<S, P>(
actionQueue: FormStateActionQueue<S, P>,
function runActionStateAction<S, P>(
actionQueue: ActionStateQueue<S, P>,
setPendingState: boolean => void,
setState: Dispatch<S | Awaited<S>>,
payload: P,
@ -1987,14 +1987,14 @@ function runFormStateAction<S, P>(
thenable.then(
(nextState: Awaited<S>) => {
actionQueue.state = nextState;
finishRunningFormStateAction(
finishRunningActionStateAction(
actionQueue,
(setPendingState: any),
(setState: any),
);
},
() =>
finishRunningFormStateAction(
finishRunningActionStateAction(
actionQueue,
(setPendingState: any),
(setState: any),
@ -2007,14 +2007,14 @@ function runFormStateAction<S, P>(
const nextState = ((returnValue: any): Awaited<S>);
actionQueue.state = nextState;
finishRunningFormStateAction(
finishRunningActionStateAction(
actionQueue,
(setPendingState: any),
(setState: any),
);
}
} catch (error) {
// This is a trick to get the `useFormState` hook to rethrow the error.
// This is a trick to get the `useActionState` hook to rethrow the error.
// When it unwraps the thenable with the `use` algorithm, the error
// will be thrown.
const rejectedThenable: S = ({
@ -2024,7 +2024,7 @@ function runFormStateAction<S, P>(
// $FlowFixMe: Not sure why this doesn't work
}: RejectedThenable<Awaited<S>>);
setState(rejectedThenable);
finishRunningFormStateAction(
finishRunningActionStateAction(
actionQueue,
(setPendingState: any),
(setState: any),
@ -2048,8 +2048,8 @@ function runFormStateAction<S, P>(
}
}
function finishRunningFormStateAction<S, P>(
actionQueue: FormStateActionQueue<S, P>,
function finishRunningActionStateAction<S, P>(
actionQueue: ActionStateQueue<S, P>,
setPendingState: Dispatch<S | Awaited<S>>,
setState: Dispatch<S | Awaited<S>>,
) {
@ -2067,7 +2067,7 @@ function finishRunningFormStateAction<S, P>(
last.next = next;
// Run the next action.
runFormStateAction(
runActionStateAction(
actionQueue,
(setPendingState: any),
(setState: any),
@ -2077,11 +2077,11 @@ function finishRunningFormStateAction<S, P>(
}
}
function formStateReducer<S>(oldState: S, newState: S): S {
function actionStateReducer<S>(oldState: S, newState: S): S {
return newState;
}
function mountFormState<S, P>(
function mountActionState<S, P>(
action: (Awaited<S>, P) => S,
initialStateProp: Awaited<S>,
permalink?: string,
@ -2113,7 +2113,7 @@ function mountFormState<S, P>(
pending: null,
lanes: NoLanes,
dispatch: (null: any),
lastRenderedReducer: formStateReducer,
lastRenderedReducer: actionStateReducer,
lastRenderedState: initialState,
};
stateHook.queue = stateQueue;
@ -2142,14 +2142,14 @@ function mountFormState<S, P>(
// but different because the actions are run sequentially, and they run in
// an event instead of during render.
const actionQueueHook = mountWorkInProgressHook();
const actionQueue: FormStateActionQueue<S, P> = {
const actionQueue: ActionStateQueue<S, P> = {
state: initialState,
dispatch: (null: any), // circular
action,
pending: null,
};
actionQueueHook.queue = actionQueue;
const dispatch = (dispatchFormState: any).bind(
const dispatch = (dispatchActionState: any).bind(
null,
currentlyRenderingFiber,
actionQueue,
@ -2166,14 +2166,14 @@ function mountFormState<S, P>(
return [initialState, dispatch, false];
}
function updateFormState<S, P>(
function updateActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
const stateHook = updateWorkInProgressHook();
const currentStateHook = ((currentHook: any): Hook);
return updateFormStateImpl(
return updateActionStateImpl(
stateHook,
currentStateHook,
action,
@ -2182,7 +2182,7 @@ function updateFormState<S, P>(
);
}
function updateFormStateImpl<S, P>(
function updateActionStateImpl<S, P>(
stateHook: Hook,
currentStateHook: Hook,
action: (Awaited<S>, P) => S,
@ -2192,7 +2192,7 @@ function updateFormStateImpl<S, P>(
const [actionResult] = updateReducerImpl<S | Thenable<S>, S | Thenable<S>>(
stateHook,
currentStateHook,
formStateReducer,
actionStateReducer,
);
const [isPending] = updateState(false);
@ -2216,7 +2216,7 @@ function updateFormStateImpl<S, P>(
currentlyRenderingFiber.flags |= PassiveEffect;
pushEffect(
HookHasEffect | HookPassive,
formStateActionEffect.bind(null, actionQueue, action),
actionStateActionEffect.bind(null, actionQueue, action),
createEffectInstance(),
null,
);
@ -2225,19 +2225,19 @@ function updateFormStateImpl<S, P>(
return [state, dispatch, isPending];
}
function formStateActionEffect<S, P>(
actionQueue: FormStateActionQueue<S, P>,
function actionStateActionEffect<S, P>(
actionQueue: ActionStateQueue<S, P>,
action: (Awaited<S>, P) => S,
): void {
actionQueue.action = action;
}
function rerenderFormState<S, P>(
function rerenderActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
// Unlike useState, useFormState doesn't support render phase updates.
// Unlike useState, useActionState doesn't support render phase updates.
// Also unlike useState, we need to replay all pending updates again in case
// the passthrough value changed.
//
@ -2249,7 +2249,7 @@ function rerenderFormState<S, P>(
if (currentStateHook !== null) {
// This is an update. Process the update queue.
return updateFormStateImpl(
return updateActionStateImpl(
stateHook,
currentStateHook,
action,
@ -3548,8 +3548,8 @@ if (enableUseEffectEventHook) {
if (enableAsyncActions) {
(HooksDispatcherOnMount: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnMount: Dispatcher).useFormState = mountFormState;
(HooksDispatcherOnMount: Dispatcher).useActionState = mountFormState;
(HooksDispatcherOnMount: Dispatcher).useFormState = mountActionState;
(HooksDispatcherOnMount: Dispatcher).useActionState = mountActionState;
}
if (enableAsyncActions) {
(HooksDispatcherOnMount: Dispatcher).useOptimistic = mountOptimistic;
@ -3587,8 +3587,8 @@ if (enableUseEffectEventHook) {
if (enableAsyncActions) {
(HooksDispatcherOnUpdate: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnUpdate: Dispatcher).useFormState = updateFormState;
(HooksDispatcherOnUpdate: Dispatcher).useActionState = updateFormState;
(HooksDispatcherOnUpdate: Dispatcher).useFormState = updateActionState;
(HooksDispatcherOnUpdate: Dispatcher).useActionState = updateActionState;
}
if (enableAsyncActions) {
(HooksDispatcherOnUpdate: Dispatcher).useOptimistic = updateOptimistic;
@ -3626,8 +3626,8 @@ if (enableUseEffectEventHook) {
if (enableAsyncActions) {
(HooksDispatcherOnRerender: Dispatcher).useHostTransitionStatus =
useHostTransitionStatus;
(HooksDispatcherOnRerender: Dispatcher).useFormState = rerenderFormState;
(HooksDispatcherOnRerender: Dispatcher).useActionState = rerenderFormState;
(HooksDispatcherOnRerender: Dispatcher).useFormState = rerenderActionState;
(HooksDispatcherOnRerender: Dispatcher).useActionState = rerenderActionState;
}
if (enableAsyncActions) {
(HooksDispatcherOnRerender: Dispatcher).useOptimistic = rerenderOptimistic;
@ -3820,7 +3820,7 @@ if (__DEV__) {
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
mountHookTypesDev();
return mountFormState(action, initialState, permalink);
return mountActionState(action, initialState, permalink);
};
(HooksDispatcherOnMountInDEV: Dispatcher).useActionState =
function useActionState<S, P>(
@ -3830,7 +3830,7 @@ if (__DEV__) {
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
mountHookTypesDev();
return mountFormState(action, initialState, permalink);
return mountActionState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
@ -4000,7 +4000,7 @@ if (__DEV__) {
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
return mountFormState(action, initialState, permalink);
return mountActionState(action, initialState, permalink);
};
(HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useActionState =
function useActionState<S, P>(
@ -4010,7 +4010,7 @@ if (__DEV__) {
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
updateHookTypesDev();
return mountFormState(action, initialState, permalink);
return mountActionState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
@ -4182,7 +4182,7 @@ if (__DEV__) {
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
return updateFormState(action, initialState, permalink);
return updateActionState(action, initialState, permalink);
};
(HooksDispatcherOnUpdateInDEV: Dispatcher).useActionState =
function useActionState<S, P>(
@ -4192,7 +4192,7 @@ if (__DEV__) {
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
updateHookTypesDev();
return updateFormState(action, initialState, permalink);
return updateActionState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
@ -4364,7 +4364,7 @@ if (__DEV__) {
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useFormState';
updateHookTypesDev();
return rerenderFormState(action, initialState, permalink);
return rerenderActionState(action, initialState, permalink);
};
(HooksDispatcherOnRerenderInDEV: Dispatcher).useActionState =
function useActionState<S, P>(
@ -4374,7 +4374,7 @@ if (__DEV__) {
): [Awaited<S>, (P) => void, boolean] {
currentHookNameInDev = 'useActionState';
updateHookTypesDev();
return rerenderFormState(action, initialState, permalink);
return rerenderActionState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
@ -4568,7 +4568,7 @@ if (__DEV__) {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
mountHookTypesDev();
return mountFormState(action, initialState, permalink);
return mountActionState(action, initialState, permalink);
};
(InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useActionState =
function useActionState<S, P>(
@ -4579,7 +4579,7 @@ if (__DEV__) {
currentHookNameInDev = 'useActionState';
warnInvalidHookAccess();
mountHookTypesDev();
return mountFormState(action, initialState, permalink);
return mountActionState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
@ -4777,7 +4777,7 @@ if (__DEV__) {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
updateHookTypesDev();
return updateFormState(action, initialState, permalink);
return updateActionState(action, initialState, permalink);
};
(InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useActionState =
function useActionState<S, P>(
@ -4788,7 +4788,7 @@ if (__DEV__) {
currentHookNameInDev = 'useActionState';
warnInvalidHookAccess();
updateHookTypesDev();
return updateFormState(action, initialState, permalink);
return updateActionState(action, initialState, permalink);
};
}
if (enableAsyncActions) {
@ -4986,7 +4986,7 @@ if (__DEV__) {
currentHookNameInDev = 'useFormState';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderFormState(action, initialState, permalink);
return rerenderActionState(action, initialState, permalink);
};
(InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useActionState =
function useActionState<S, P>(
@ -4997,7 +4997,7 @@ if (__DEV__) {
currentHookNameInDev = 'useActionState';
warnInvalidHookAccess();
updateHookTypesDev();
return rerenderFormState(action, initialState, permalink);
return rerenderActionState(action, initialState, permalink);
};
}
if (enableAsyncActions) {

View File

@ -439,7 +439,7 @@ export function tryToClaimNextHydratableFormMarkerInstance(
}
// Should have found a marker instance. Throw an error to trigger client
// rendering. We don't bother to check if we're in a concurrent root because
// useFormState is a new API, so backwards compat is not an issue.
// useActionState is a new API, so backwards compat is not an issue.
throwOnHydrationMismatch(fiber);
return false;
}

View File

@ -892,7 +892,7 @@ describe('ReactFlightDOMForm', () => {
});
// @gate enableAsyncActions
it('useFormState can return JSX state during MPA form submission', async () => {
it('useActionState can return JSX state during MPA form submission', async () => {
const serverAction = serverExports(
async function action(prevState, formData) {
return <div>error message</div>;

View File

@ -78,11 +78,11 @@ let didScheduleRenderPhaseUpdate: boolean = false;
let localIdCounter: number = 0;
// Chunks that should be pushed to the stream once the component
// finishes rendering.
// Counts the number of useFormState calls in this component
let formStateCounter: number = 0;
// The index of the useFormState hook that matches the one passed in at the
// Counts the number of useActionState calls in this component
let actionStateCounter: number = 0;
// The index of the useActionState hook that matches the one passed in at the
// root during an MPA navigation, if any.
let formStateMatchingIndex: number = -1;
let actionStateMatchingIndex: number = -1;
// Counts the number of use(thenable) calls in this component
let thenableIndexCounter: number = 0;
let thenableState: ThenableState | null = null;
@ -223,8 +223,8 @@ export function prepareToUseHooks(
// workInProgressHook = null;
localIdCounter = 0;
formStateCounter = 0;
formStateMatchingIndex = -1;
actionStateCounter = 0;
actionStateMatchingIndex = -1;
thenableIndexCounter = 0;
thenableState = prevThenableState;
}
@ -245,8 +245,8 @@ export function finishHooks(
// restarting until no more updates are scheduled.
didScheduleRenderPhaseUpdate = false;
localIdCounter = 0;
formStateCounter = 0;
formStateMatchingIndex = -1;
actionStateCounter = 0;
actionStateMatchingIndex = -1;
thenableIndexCounter = 0;
numberOfReRenders += 1;
@ -274,17 +274,17 @@ export function checkDidRenderIdHook(): boolean {
return didRenderIdHook;
}
export function getFormStateCount(): number {
export function getActionStateCount(): number {
// This should be called immediately after every finishHooks call.
// Conceptually, it's part of the return value of finishHooks; it's only a
// separate function to avoid using an array tuple.
return formStateCounter;
return actionStateCounter;
}
export function getFormStateMatchingIndex(): number {
export function getActionStateMatchingIndex(): number {
// This should be called immediately after every finishHooks call.
// Conceptually, it's part of the return value of finishHooks; it's only a
// separate function to avoid using an array tuple.
return formStateMatchingIndex;
return actionStateMatchingIndex;
}
// Reset the internal hooks state if an error occurs while rendering a component
@ -591,7 +591,7 @@ function useOptimistic<S, A>(
return [passthrough, unsupportedSetOptimisticState];
}
function createPostbackFormStateKey(
function createPostbackActionStateKey(
permalink: string | void,
componentKeyPath: KeyNode | null,
hookIndex: number,
@ -610,17 +610,17 @@ function createPostbackFormStateKey(
}
}
function useFormState<S, P>(
function useActionState<S, P>(
action: (Awaited<S>, P) => S,
initialState: Awaited<S>,
permalink?: string,
): [Awaited<S>, (P) => void, boolean] {
resolveCurrentlyRenderingComponent();
// Count the number of useFormState hooks per component. We also use this to
// track the position of this useFormState hook relative to the other ones in
// Count the number of useActionState hooks per component. We also use this to
// track the position of this useActionState hook relative to the other ones in
// this component, so we can generate a unique key for each one.
const formStateHookIndex = formStateCounter++;
const actionStateHookIndex = actionStateCounter++;
const request: Request = (currentlyRenderingRequest: any);
// $FlowIgnore[prop-missing]
@ -629,7 +629,7 @@ function useFormState<S, P>(
// This is a server action. These have additional features to enable
// MPA-style form submissions with progressive enhancement.
// TODO: If the same permalink is passed to multiple useFormStates, and
// TODO: If the same permalink is passed to multiple useActionStates, and
// they all have the same action signature, Fizz will pass the postback
// state to all of them. We should probably only pass it to the first one,
// and/or warn.
@ -640,30 +640,33 @@ function useFormState<S, P>(
// Determine the current form state. If we received state during an MPA form
// submission, then we will reuse that, if the action identity matches.
// Otherwise we'll use the initial state argument. We will emit a comment
// Otherwise, we'll use the initial state argument. We will emit a comment
// marker into the stream that indicates whether the state was reused.
let state = initialState;
const componentKeyPath = (currentlyRenderingKeyPath: any);
const postbackFormState = getFormState(request);
const postbackActionState = getFormState(request);
// $FlowIgnore[prop-missing]
const isSignatureEqual = action.$$IS_SIGNATURE_EQUAL;
if (postbackFormState !== null && typeof isSignatureEqual === 'function') {
const postbackKey = postbackFormState[1];
const postbackReferenceId = postbackFormState[2];
const postbackBoundArity = postbackFormState[3];
if (
postbackActionState !== null &&
typeof isSignatureEqual === 'function'
) {
const postbackKey = postbackActionState[1];
const postbackReferenceId = postbackActionState[2];
const postbackBoundArity = postbackActionState[3];
if (
isSignatureEqual.call(action, postbackReferenceId, postbackBoundArity)
) {
nextPostbackStateKey = createPostbackFormStateKey(
nextPostbackStateKey = createPostbackActionStateKey(
permalink,
componentKeyPath,
formStateHookIndex,
actionStateHookIndex,
);
if (postbackKey === nextPostbackStateKey) {
// This was a match
formStateMatchingIndex = formStateHookIndex;
actionStateMatchingIndex = actionStateHookIndex;
// Reuse the state that was submitted by the form.
state = postbackFormState[0];
state = postbackActionState[0];
}
}
}
@ -695,10 +698,10 @@ function useFormState<S, P>(
const formData = metadata.data;
if (formData) {
if (nextPostbackStateKey === null) {
nextPostbackStateKey = createPostbackFormStateKey(
nextPostbackStateKey = createPostbackActionStateKey(
permalink,
componentKeyPath,
formStateHookIndex,
actionStateHookIndex,
);
}
formData.append('$ACTION_KEY', nextPostbackStateKey);
@ -818,8 +821,8 @@ if (enableAsyncActions) {
}
if (enableAsyncActions) {
HooksDispatcher.useOptimistic = useOptimistic;
HooksDispatcher.useFormState = useFormState;
HooksDispatcher.useActionState = useFormState;
HooksDispatcher.useFormState = useActionState;
HooksDispatcher.useActionState = useActionState;
}
export let currentResumableState: null | ResumableState = (null: any);

View File

@ -106,8 +106,8 @@ import {
setCurrentResumableState,
getThenableStateAfterSuspending,
unwrapThenable,
getFormStateCount,
getFormStateMatchingIndex,
getActionStateCount,
getActionStateMatchingIndex,
} from './ReactFizzHooks';
import {DefaultCacheDispatcher} from './ReactFizzCache';
import {getStackByComponentStackNode} from './ReactFizzComponentStack';
@ -1440,8 +1440,8 @@ function renderIndeterminateComponent(
legacyContext,
);
const hasId = checkDidRenderIdHook();
const formStateCount = getFormStateCount();
const formStateMatchingIndex = getFormStateMatchingIndex();
const actionStateCount = getActionStateCount();
const actionStateMatchingIndex = getActionStateMatchingIndex();
if (__DEV__) {
// Support for module components is deprecated and is removed behind a flag.
@ -1517,8 +1517,8 @@ function renderIndeterminateComponent(
keyPath,
value,
hasId,
formStateCount,
formStateMatchingIndex,
actionStateCount,
actionStateMatchingIndex,
);
}
task.componentStack = previousComponentStack;
@ -1530,22 +1530,22 @@ function finishFunctionComponent(
keyPath: KeyNode,
children: ReactNodeList,
hasId: boolean,
formStateCount: number,
formStateMatchingIndex: number,
actionStateCount: number,
actionStateMatchingIndex: number,
) {
let didEmitFormStateMarkers = false;
if (formStateCount !== 0 && request.formState !== null) {
// For each useFormState hook, emit a marker that indicates whether we
let didEmitActionStateMarkers = false;
if (actionStateCount !== 0 && request.formState !== null) {
// For each useActionState hook, emit a marker that indicates whether we
// rendered using the form state passed at the root. We only emit these
// markers if form state is passed at the root.
const segment = task.blockedSegment;
if (segment === null) {
// Implies we're in reumable mode.
} else {
didEmitFormStateMarkers = true;
didEmitActionStateMarkers = true;
const target = segment.chunks;
for (let i = 0; i < formStateCount; i++) {
if (i === formStateMatchingIndex) {
for (let i = 0; i < actionStateCount; i++) {
if (i === actionStateMatchingIndex) {
pushFormStateMarkerIsMatching(target);
} else {
pushFormStateMarkerIsNotMatching(target);
@ -1569,8 +1569,8 @@ function finishFunctionComponent(
// Like the other contexts, this does not need to be in a finally block
// because renderNode takes care of unwinding the stack.
task.treeContext = prevTreeContext;
} else if (didEmitFormStateMarkers) {
// If there were formState hooks, we must use the non-destructive path
} else if (didEmitActionStateMarkers) {
// If there were useActionState hooks, we must use the non-destructive path
// because this component is not a pure indirection; we emitted markers
// to the stream.
renderNode(request, task, children, -1);
@ -1690,16 +1690,16 @@ function renderForwardRef(
ref,
);
const hasId = checkDidRenderIdHook();
const formStateCount = getFormStateCount();
const formStateMatchingIndex = getFormStateMatchingIndex();
const actionStateCount = getActionStateCount();
const actionStateMatchingIndex = getActionStateMatchingIndex();
finishFunctionComponent(
request,
task,
keyPath,
children,
hasId,
formStateCount,
formStateMatchingIndex,
actionStateCount,
actionStateMatchingIndex,
);
task.componentStack = previousComponentStack;
}

View File

@ -10,7 +10,7 @@
// A pure JS implementation of a string hashing function. We do not use it for
// security or obfuscation purposes, only to create compact hashes. So we
// prioritize speed over collision avoidance. For example, we use this to hash
// the component key path used by useFormState for MPA-style submissions.
// the component key path used by useActionState for MPA-style submissions.
//
// In environments where built-in hashing functions are available, we prefer
// those instead. Like Node's crypto module, or Bun.hash. Unfortunately this