mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
s/form state/action state (#28631)
Rename internals from "form state" to "action state"
This commit is contained in:
parent
299a9c0598
commit
05797ccebd
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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++;
|
||||
|
|
|
|||
110
packages/react-reconciler/src/ReactFiberHooks.js
vendored
110
packages/react-reconciler/src/ReactFiberHooks.js
vendored
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
|
|
|
|||
67
packages/react-server/src/ReactFizzHooks.js
vendored
67
packages/react-server/src/ReactFizzHooks.js
vendored
|
|
@ -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);
|
||||
|
|
|
|||
40
packages/react-server/src/ReactFizzServer.js
vendored
40
packages/react-server/src/ReactFizzServer.js
vendored
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user