Skip to content

Commit

Permalink
[useFormState] Allow sync actions (#27571)
Browse files Browse the repository at this point in the history
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 [77c4ac2](77c4ac2)
  • Loading branch information
acdlite committed Nov 1, 2023
1 parent 8c6e716 commit 0d03ff9
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 107 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ca16c26356221a4b52185d7425d77675f0307250
77c4ac2ce88736bbdfe0b29008b5df931c2beb1e
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -587,4 +587,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-www-classic-5fd0277f";
exports.version = "18.3.0-www-classic-846422de";
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -579,4 +579,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-www-modern-d01608e2";
exports.version = "18.3.0-www-modern-bf51fb82";
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactART-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
return self;
}

var ReactVersion = "18.3.0-www-classic-a2f705e2";
var ReactVersion = "18.3.0-www-classic-44d9eb8b";

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -9961,12 +9961,12 @@ function startTransition(
// This is either `finishedState` or a thenable that resolves to
// `finishedState`, depending on whether we're inside an async
// action scope.
var _entangledResult = requestSyncActionContext(
var _entangledResult2 = requestSyncActionContext(
returnValue,
finishedState
);

dispatchSetState(fiber, queue, _entangledResult);
dispatchSetState(fiber, queue, _entangledResult2);
}
} else {
// Async actions are not enabled.
Expand Down
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactART-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
return self;
}

var ReactVersion = "18.3.0-www-modern-8895715e";
var ReactVersion = "18.3.0-www-modern-044f7cdc";

var LegacyRoot = 0;
var ConcurrentRoot = 1;
Expand Down Expand Up @@ -9717,12 +9717,12 @@ function startTransition(
// This is either `finishedState` or a thenable that resolves to
// `finishedState`, depending on whether we're inside an async
// action scope.
var _entangledResult = requestSyncActionContext(
var _entangledResult2 = requestSyncActionContext(
returnValue,
finishedState
);

dispatchSetState(fiber, queue, _entangledResult);
dispatchSetState(fiber, queue, _entangledResult2);
}
} else {
// Async actions are not enabled.
Expand Down
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactDOM-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -14527,12 +14527,12 @@ function startTransition(
// This is either `finishedState` or a thenable that resolves to
// `finishedState`, depending on whether we're inside an async
// action scope.
var _entangledResult = requestSyncActionContext(
var _entangledResult2 = requestSyncActionContext(
returnValue,
finishedState
);

dispatchSetState(fiber, queue, _entangledResult);
dispatchSetState(fiber, queue, _entangledResult2);
}
} else {
// Async actions are not enabled.
Expand Down Expand Up @@ -34164,7 +34164,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-www-classic-e9edf038";
var ReactVersion = "18.3.0-www-classic-92999e24";

function createPortal$1(
children,
Expand Down
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactDOM-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -14468,12 +14468,12 @@ function startTransition(
// This is either `finishedState` or a thenable that resolves to
// `finishedState`, depending on whether we're inside an async
// action scope.
var _entangledResult = requestSyncActionContext(
var _entangledResult2 = requestSyncActionContext(
returnValue,
finishedState
);

dispatchSetState(fiber, queue, _entangledResult);
dispatchSetState(fiber, queue, _entangledResult2);
}
} else {
// Async actions are not enabled.
Expand Down Expand Up @@ -34009,7 +34009,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-www-modern-fb6585b7";
var ReactVersion = "18.3.0-www-modern-2befbb3a";

function createPortal$1(
children,
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/ReactDOMServer-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ if (__DEV__) {
var React = require("react");
var ReactDOM = require("react-dom");

var ReactVersion = "18.3.0-www-modern-8895715e";
var ReactVersion = "18.3.0-www-modern-044f7cdc";

// This refers to a WWW module.
var warningWWW = require("warning");
Expand Down
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactDOMTesting-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -14661,12 +14661,12 @@ function startTransition(
// This is either `finishedState` or a thenable that resolves to
// `finishedState`, depending on whether we're inside an async
// action scope.
var _entangledResult = requestSyncActionContext(
var _entangledResult2 = requestSyncActionContext(
returnValue,
finishedState
);

dispatchSetState(fiber, queue, _entangledResult);
dispatchSetState(fiber, queue, _entangledResult2);
}
} else {
// Async actions are not enabled.
Expand Down Expand Up @@ -34781,7 +34781,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-www-classic-91f20f7a";
var ReactVersion = "18.3.0-www-classic-ec74cb13";

function createPortal$1(
children,
Expand Down
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactDOMTesting-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -14602,12 +14602,12 @@ function startTransition(
// This is either `finishedState` or a thenable that resolves to
// `finishedState`, depending on whether we're inside an async
// action scope.
var _entangledResult = requestSyncActionContext(
var _entangledResult2 = requestSyncActionContext(
returnValue,
finishedState
);

dispatchSetState(fiber, queue, _entangledResult);
dispatchSetState(fiber, queue, _entangledResult2);
}
} else {
// Async actions are not enabled.
Expand Down Expand Up @@ -34626,7 +34626,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-www-modern-d01608e2";
var ReactVersion = "18.3.0-www-modern-bf51fb82";

function createPortal$1(
children,
Expand Down
98 changes: 56 additions & 42 deletions compiled/facebook-www/ReactTestRenderer-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -8347,34 +8347,48 @@ function runFormStateAction(actionQueue, setState, payload) {
}

try {
var promise = action(prevState, payload);
var returnValue = action(prevState, payload);

if (true) {
if (
promise === null ||
typeof promise !== "object" ||
typeof promise.then !== "function"
) {
error("The action passed to useFormState must be an async function.");
}
} // Attach a listener to read the return state of the action. As soon as this
// resolves, we can run the next action in the sequence.

promise.then(
function (nextState) {
actionQueue.state = nextState;
finishRunningFormStateAction(actionQueue, setState);
},
function () {
return finishRunningFormStateAction(actionQueue, setState);
}
); // Create a thenable that resolves once the current async action scope has
// finished. Then stash that thenable in state. We'll unwrap it with the
// `use` algorithm during render. This is the same logic used
// by startTransition.
if (
returnValue !== null &&
typeof returnValue === "object" && // $FlowFixMe[method-unbinding]
typeof returnValue.then === "function"
) {
var thenable = returnValue; // Attach a listener to read the return state of the action. As soon as
// this resolves, we can run the next action in the sequence.

thenable.then(
function (nextState) {
actionQueue.state = nextState;
finishRunningFormStateAction(actionQueue, setState);
},
function () {
return finishRunningFormStateAction(actionQueue, setState);
}
);
var entangledResult = requestAsyncActionContext(thenable, null);
setState(entangledResult);
} else {
// This is either `returnValue` or a thenable that resolves to
// `returnValue`, depending on whether we're inside an async action scope.
var _entangledResult = requestSyncActionContext(returnValue, null);

var entangledThenable = requestAsyncActionContext(promise, null);
setState(entangledThenable);
setState(_entangledResult);
var nextState = returnValue;
actionQueue.state = nextState;
finishRunningFormStateAction(actionQueue, setState);
}
} catch (error) {
// This is a trick to get the `useFormState` hook to rethrow the error.
// When it unwraps the thenable with the `use` algorithm, the error
// will be thrown.
var rejectedThenable = {
then: function () {},
status: "rejected",
reason: error // $FlowFixMe: Not sure why this doesn't work
};
setState(rejectedThenable);
finishRunningFormStateAction(actionQueue, setState);
} finally {
ReactCurrentBatchConfig$2.transition = prevTransition;

Expand Down Expand Up @@ -8423,22 +8437,18 @@ function formStateReducer(oldState, newState) {

function mountFormState(action, initialStateProp, permalink) {
var initialState = initialStateProp;

var initialStateThenable = {
status: "fulfilled",
value: initialState,
then: function () {}
}; // State hook. The state is stored in a thenable which is then unwrapped by
// the `use` algorithm during render.

var stateHook = mountWorkInProgressHook();
stateHook.memoizedState = stateHook.baseState = initialStateThenable;
stateHook.memoizedState = stateHook.baseState = initialState; // TODO: Typing this "correctly" results in recursion limit errors
// const stateQueue: UpdateQueue<S | Awaited<S>, S | Awaited<S>> = {

var stateQueue = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: formStateReducer,
lastRenderedState: initialStateThenable
lastRenderedState: initialState
};
stateHook.queue = stateQueue;
var setState = dispatchSetState.bind(
Expand Down Expand Up @@ -8492,9 +8502,14 @@ function updateFormStateImpl(
currentStateHook,
formStateReducer
),
thenable = _updateReducerImpl[0]; // This will suspend until the action finishes.

var state = useThenable(thenable);
actionResult = _updateReducerImpl[0]; // This will suspend until the action finishes.

var state =
typeof actionResult === "object" &&
actionResult !== null && // $FlowFixMe[method-unbinding]
typeof actionResult.then === "function"
? useThenable(actionResult)
: actionResult;
var actionQueueHook = updateWorkInProgressHook();
var actionQueue = actionQueueHook.queue;
var dispatch = actionQueue.dispatch; // Check if a new action was passed. If so, update it in an effect.
Expand Down Expand Up @@ -8534,8 +8549,7 @@ function rerenderFormState(action, initialState, permalink) {
return updateFormStateImpl(stateHook, currentStateHook, action);
} // This is a mount. No updates to process.

var thenable = stateHook.memoizedState;
var state = useThenable(thenable);
var state = stateHook.memoizedState;
var actionQueueHook = updateWorkInProgressHook();
var actionQueue = actionQueueHook.queue;
var dispatch = actionQueue.dispatch; // This may have changed during the rerender.
Expand Down Expand Up @@ -8980,12 +8994,12 @@ function startTransition(
// This is either `finishedState` or a thenable that resolves to
// `finishedState`, depending on whether we're inside an async
// action scope.
var _entangledResult = requestSyncActionContext(
var _entangledResult2 = requestSyncActionContext(
returnValue,
finishedState
);

dispatchSetState(fiber, queue, _entangledResult);
dispatchSetState(fiber, queue, _entangledResult2);
}
}
} catch (error) {
Expand Down Expand Up @@ -25267,7 +25281,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-www-classic-5fd0277f";
var ReactVersion = "18.3.0-www-classic-846422de";

// Might add PROFILE later.

Expand Down
Loading

0 comments on commit 0d03ff9

Please sign in to comment.