Skip to content

Commit fcfdb89

Browse files
committed
[useFormState] Allow sync actions (#27571)
Updates useFormState to allow a sync function to be passed as an action. A form action is almost always async, because it needs to talk to the server. But since we support client-side actions, too, there's no reason we can't allow sync actions, too. I originally chose not to allow them to keep the implementation simpler but it's not really that much more complicated because we already support this for actions passed to startTransition. So now it's consistent: anywhere an action is accepted, a sync client function is a valid input. DiffTrain build for commit 77c4ac2.
1 parent 956de72 commit fcfdb89

File tree

9 files changed

+332
-290
lines changed

9 files changed

+332
-290
lines changed

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react-test-renderer/cjs/ReactTestRenderer-dev.js

Lines changed: 57 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<cbd8ea0713685000d0b28083e1b0ef92>>
10+
* @generated SignedSource<<ae1a8bf6624e4c6f9e70730750430a22>>
1111
*/
1212

1313
'use strict';
@@ -8071,34 +8071,48 @@ function runFormStateAction(actionQueue, setState, payload) {
80718071
}
80728072

80738073
try {
8074-
var promise = action(prevState, payload);
8074+
var returnValue = action(prevState, payload);
80758075

8076-
if (true) {
8077-
if (
8078-
promise === null ||
8079-
typeof promise !== "object" ||
8080-
typeof promise.then !== "function"
8081-
) {
8082-
error("The action passed to useFormState must be an async function.");
8083-
}
8084-
} // Attach a listener to read the return state of the action. As soon as this
8085-
// resolves, we can run the next action in the sequence.
8086-
8087-
promise.then(
8088-
function (nextState) {
8089-
actionQueue.state = nextState;
8090-
finishRunningFormStateAction(actionQueue, setState);
8091-
},
8092-
function () {
8093-
return finishRunningFormStateAction(actionQueue, setState);
8094-
}
8095-
); // Create a thenable that resolves once the current async action scope has
8096-
// finished. Then stash that thenable in state. We'll unwrap it with the
8097-
// `use` algorithm during render. This is the same logic used
8098-
// by startTransition.
8076+
if (
8077+
returnValue !== null &&
8078+
typeof returnValue === "object" && // $FlowFixMe[method-unbinding]
8079+
typeof returnValue.then === "function"
8080+
) {
8081+
var thenable = returnValue; // Attach a listener to read the return state of the action. As soon as
8082+
// this resolves, we can run the next action in the sequence.
8083+
8084+
thenable.then(
8085+
function (nextState) {
8086+
actionQueue.state = nextState;
8087+
finishRunningFormStateAction(actionQueue, setState);
8088+
},
8089+
function () {
8090+
return finishRunningFormStateAction(actionQueue, setState);
8091+
}
8092+
);
8093+
var entangledResult = requestAsyncActionContext(thenable, null);
8094+
setState(entangledResult);
8095+
} else {
8096+
// This is either `returnValue` or a thenable that resolves to
8097+
// `returnValue`, depending on whether we're inside an async action scope.
8098+
var _entangledResult = requestSyncActionContext(returnValue, null);
80998099

8100-
var entangledThenable = requestAsyncActionContext(promise, null);
8101-
setState(entangledThenable);
8100+
setState(_entangledResult);
8101+
var nextState = returnValue;
8102+
actionQueue.state = nextState;
8103+
finishRunningFormStateAction(actionQueue, setState);
8104+
}
8105+
} catch (error) {
8106+
// This is a trick to get the `useFormState` hook to rethrow the error.
8107+
// When it unwraps the thenable with the `use` algorithm, the error
8108+
// will be thrown.
8109+
var rejectedThenable = {
8110+
then: function () {},
8111+
status: "rejected",
8112+
reason: error // $FlowFixMe: Not sure why this doesn't work
8113+
};
8114+
setState(rejectedThenable);
8115+
finishRunningFormStateAction(actionQueue, setState);
81028116
} finally {
81038117
ReactCurrentBatchConfig$2.transition = prevTransition;
81048118

@@ -8147,22 +8161,18 @@ function formStateReducer(oldState, newState) {
81478161

81488162
function mountFormState(action, initialStateProp, permalink) {
81498163
var initialState = initialStateProp;
8150-
8151-
var initialStateThenable = {
8152-
status: "fulfilled",
8153-
value: initialState,
8154-
then: function () {}
8155-
}; // State hook. The state is stored in a thenable which is then unwrapped by
81568164
// the `use` algorithm during render.
81578165

81588166
var stateHook = mountWorkInProgressHook();
8159-
stateHook.memoizedState = stateHook.baseState = initialStateThenable;
8167+
stateHook.memoizedState = stateHook.baseState = initialState; // TODO: Typing this "correctly" results in recursion limit errors
8168+
// const stateQueue: UpdateQueue<S | Awaited<S>, S | Awaited<S>> = {
8169+
81608170
var stateQueue = {
81618171
pending: null,
81628172
lanes: NoLanes,
81638173
dispatch: null,
81648174
lastRenderedReducer: formStateReducer,
8165-
lastRenderedState: initialStateThenable
8175+
lastRenderedState: initialState
81668176
};
81678177
stateHook.queue = stateQueue;
81688178
var setState = dispatchSetState.bind(
@@ -8216,9 +8226,14 @@ function updateFormStateImpl(
82168226
currentStateHook,
82178227
formStateReducer
82188228
),
8219-
thenable = _updateReducerImpl[0]; // This will suspend until the action finishes.
8220-
8221-
var state = useThenable(thenable);
8229+
actionResult = _updateReducerImpl[0]; // This will suspend until the action finishes.
8230+
8231+
var state =
8232+
typeof actionResult === "object" &&
8233+
actionResult !== null && // $FlowFixMe[method-unbinding]
8234+
typeof actionResult.then === "function"
8235+
? useThenable(actionResult)
8236+
: actionResult;
82228237
var actionQueueHook = updateWorkInProgressHook();
82238238
var actionQueue = actionQueueHook.queue;
82248239
var dispatch = actionQueue.dispatch; // Check if a new action was passed. If so, update it in an effect.
@@ -8258,8 +8273,7 @@ function rerenderFormState(action, initialState, permalink) {
82588273
return updateFormStateImpl(stateHook, currentStateHook, action);
82598274
} // This is a mount. No updates to process.
82608275

8261-
var thenable = stateHook.memoizedState;
8262-
var state = useThenable(thenable);
8276+
var state = stateHook.memoizedState;
82638277
var actionQueueHook = updateWorkInProgressHook();
82648278
var actionQueue = actionQueueHook.queue;
82658279
var dispatch = actionQueue.dispatch; // This may have changed during the rerender.
@@ -8686,12 +8700,12 @@ function startTransition(
86868700
// This is either `finishedState` or a thenable that resolves to
86878701
// `finishedState`, depending on whether we're inside an async
86888702
// action scope.
8689-
var _entangledResult = requestSyncActionContext(
8703+
var _entangledResult2 = requestSyncActionContext(
86908704
returnValue,
86918705
finishedState
86928706
);
86938707

8694-
dispatchSetState(fiber, queue, _entangledResult);
8708+
dispatchSetState(fiber, queue, _entangledResult2);
86958709
}
86968710
}
86978711
} catch (error) {
@@ -24887,7 +24901,7 @@ function createFiberRoot(
2488724901
return root;
2488824902
}
2488924903

24890-
var ReactVersion = "18.3.0-canary-08a39539f-20231031";
24904+
var ReactVersion = "18.3.0-canary-77c4ac2ce-20231031";
2489124905

2489224906
// Might add PROFILE later.
2489324907

0 commit comments

Comments
 (0)