Skip to content

Commit 0d03ff9

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 [77c4ac2](77c4ac2)
1 parent 8c6e716 commit 0d03ff9

13 files changed

+134
-107
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
ca16c26356221a4b52185d7425d77675f0307250
1+
77c4ac2ce88736bbdfe0b29008b5df931c2beb1e

compiled/facebook-www/React-prod.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,4 +587,4 @@ exports.useSyncExternalStore = function (
587587
exports.useTransition = function () {
588588
return ReactCurrentDispatcher.current.useTransition();
589589
};
590-
exports.version = "18.3.0-www-classic-5fd0277f";
590+
exports.version = "18.3.0-www-classic-846422de";

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -579,4 +579,4 @@ exports.useSyncExternalStore = function (
579579
exports.useTransition = function () {
580580
return ReactCurrentDispatcher.current.useTransition();
581581
};
582-
exports.version = "18.3.0-www-modern-d01608e2";
582+
exports.version = "18.3.0-www-modern-bf51fb82";

compiled/facebook-www/ReactART-dev.classic.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

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

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;
@@ -9961,12 +9961,12 @@ function startTransition(
99619961
// This is either `finishedState` or a thenable that resolves to
99629962
// `finishedState`, depending on whether we're inside an async
99639963
// action scope.
9964-
var _entangledResult = requestSyncActionContext(
9964+
var _entangledResult2 = requestSyncActionContext(
99659965
returnValue,
99669966
finishedState
99679967
);
99689968

9969-
dispatchSetState(fiber, queue, _entangledResult);
9969+
dispatchSetState(fiber, queue, _entangledResult2);
99709970
}
99719971
} else {
99729972
// Async actions are not enabled.

compiled/facebook-www/ReactART-dev.modern.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function _assertThisInitialized(self) {
6969
return self;
7070
}
7171

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

7474
var LegacyRoot = 0;
7575
var ConcurrentRoot = 1;
@@ -9717,12 +9717,12 @@ function startTransition(
97179717
// This is either `finishedState` or a thenable that resolves to
97189718
// `finishedState`, depending on whether we're inside an async
97199719
// action scope.
9720-
var _entangledResult = requestSyncActionContext(
9720+
var _entangledResult2 = requestSyncActionContext(
97219721
returnValue,
97229722
finishedState
97239723
);
97249724

9725-
dispatchSetState(fiber, queue, _entangledResult);
9725+
dispatchSetState(fiber, queue, _entangledResult2);
97269726
}
97279727
} else {
97289728
// Async actions are not enabled.

compiled/facebook-www/ReactDOM-dev.classic.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14527,12 +14527,12 @@ function startTransition(
1452714527
// This is either `finishedState` or a thenable that resolves to
1452814528
// `finishedState`, depending on whether we're inside an async
1452914529
// action scope.
14530-
var _entangledResult = requestSyncActionContext(
14530+
var _entangledResult2 = requestSyncActionContext(
1453114531
returnValue,
1453214532
finishedState
1453314533
);
1453414534

14535-
dispatchSetState(fiber, queue, _entangledResult);
14535+
dispatchSetState(fiber, queue, _entangledResult2);
1453614536
}
1453714537
} else {
1453814538
// Async actions are not enabled.
@@ -34164,7 +34164,7 @@ function createFiberRoot(
3416434164
return root;
3416534165
}
3416634166

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

3416934169
function createPortal$1(
3417034170
children,

compiled/facebook-www/ReactDOM-dev.modern.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14468,12 +14468,12 @@ function startTransition(
1446814468
// This is either `finishedState` or a thenable that resolves to
1446914469
// `finishedState`, depending on whether we're inside an async
1447014470
// action scope.
14471-
var _entangledResult = requestSyncActionContext(
14471+
var _entangledResult2 = requestSyncActionContext(
1447214472
returnValue,
1447314473
finishedState
1447414474
);
1447514475

14476-
dispatchSetState(fiber, queue, _entangledResult);
14476+
dispatchSetState(fiber, queue, _entangledResult2);
1447714477
}
1447814478
} else {
1447914479
// Async actions are not enabled.
@@ -34009,7 +34009,7 @@ function createFiberRoot(
3400934009
return root;
3401034010
}
3401134011

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

3401434014
function createPortal$1(
3401534015
children,

compiled/facebook-www/ReactDOMServer-dev.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if (__DEV__) {
1919
var React = require("react");
2020
var ReactDOM = require("react-dom");
2121

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

2424
// This refers to a WWW module.
2525
var warningWWW = require("warning");

compiled/facebook-www/ReactDOMTesting-dev.classic.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14661,12 +14661,12 @@ function startTransition(
1466114661
// This is either `finishedState` or a thenable that resolves to
1466214662
// `finishedState`, depending on whether we're inside an async
1466314663
// action scope.
14664-
var _entangledResult = requestSyncActionContext(
14664+
var _entangledResult2 = requestSyncActionContext(
1466514665
returnValue,
1466614666
finishedState
1466714667
);
1466814668

14669-
dispatchSetState(fiber, queue, _entangledResult);
14669+
dispatchSetState(fiber, queue, _entangledResult2);
1467014670
}
1467114671
} else {
1467214672
// Async actions are not enabled.
@@ -34781,7 +34781,7 @@ function createFiberRoot(
3478134781
return root;
3478234782
}
3478334783

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

3478634786
function createPortal$1(
3478734787
children,

compiled/facebook-www/ReactDOMTesting-dev.modern.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14602,12 +14602,12 @@ function startTransition(
1460214602
// This is either `finishedState` or a thenable that resolves to
1460314603
// `finishedState`, depending on whether we're inside an async
1460414604
// action scope.
14605-
var _entangledResult = requestSyncActionContext(
14605+
var _entangledResult2 = requestSyncActionContext(
1460614606
returnValue,
1460714607
finishedState
1460814608
);
1460914609

14610-
dispatchSetState(fiber, queue, _entangledResult);
14610+
dispatchSetState(fiber, queue, _entangledResult2);
1461114611
}
1461214612
} else {
1461314613
// Async actions are not enabled.
@@ -34626,7 +34626,7 @@ function createFiberRoot(
3462634626
return root;
3462734627
}
3462834628

34629-
var ReactVersion = "18.3.0-www-modern-d01608e2";
34629+
var ReactVersion = "18.3.0-www-modern-bf51fb82";
3463034630

3463134631
function createPortal$1(
3463234632
children,

compiled/facebook-www/ReactTestRenderer-dev.classic.js

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -8347,34 +8347,48 @@ function runFormStateAction(actionQueue, setState, payload) {
83478347
}
83488348

83498349
try {
8350-
var promise = action(prevState, payload);
8350+
var returnValue = action(prevState, payload);
83518351

8352-
if (true) {
8353-
if (
8354-
promise === null ||
8355-
typeof promise !== "object" ||
8356-
typeof promise.then !== "function"
8357-
) {
8358-
error("The action passed to useFormState must be an async function.");
8359-
}
8360-
} // Attach a listener to read the return state of the action. As soon as this
8361-
// resolves, we can run the next action in the sequence.
8362-
8363-
promise.then(
8364-
function (nextState) {
8365-
actionQueue.state = nextState;
8366-
finishRunningFormStateAction(actionQueue, setState);
8367-
},
8368-
function () {
8369-
return finishRunningFormStateAction(actionQueue, setState);
8370-
}
8371-
); // Create a thenable that resolves once the current async action scope has
8372-
// finished. Then stash that thenable in state. We'll unwrap it with the
8373-
// `use` algorithm during render. This is the same logic used
8374-
// by startTransition.
8352+
if (
8353+
returnValue !== null &&
8354+
typeof returnValue === "object" && // $FlowFixMe[method-unbinding]
8355+
typeof returnValue.then === "function"
8356+
) {
8357+
var thenable = returnValue; // Attach a listener to read the return state of the action. As soon as
8358+
// this resolves, we can run the next action in the sequence.
8359+
8360+
thenable.then(
8361+
function (nextState) {
8362+
actionQueue.state = nextState;
8363+
finishRunningFormStateAction(actionQueue, setState);
8364+
},
8365+
function () {
8366+
return finishRunningFormStateAction(actionQueue, setState);
8367+
}
8368+
);
8369+
var entangledResult = requestAsyncActionContext(thenable, null);
8370+
setState(entangledResult);
8371+
} else {
8372+
// This is either `returnValue` or a thenable that resolves to
8373+
// `returnValue`, depending on whether we're inside an async action scope.
8374+
var _entangledResult = requestSyncActionContext(returnValue, null);
83758375

8376-
var entangledThenable = requestAsyncActionContext(promise, null);
8377-
setState(entangledThenable);
8376+
setState(_entangledResult);
8377+
var nextState = returnValue;
8378+
actionQueue.state = nextState;
8379+
finishRunningFormStateAction(actionQueue, setState);
8380+
}
8381+
} catch (error) {
8382+
// This is a trick to get the `useFormState` hook to rethrow the error.
8383+
// When it unwraps the thenable with the `use` algorithm, the error
8384+
// will be thrown.
8385+
var rejectedThenable = {
8386+
then: function () {},
8387+
status: "rejected",
8388+
reason: error // $FlowFixMe: Not sure why this doesn't work
8389+
};
8390+
setState(rejectedThenable);
8391+
finishRunningFormStateAction(actionQueue, setState);
83788392
} finally {
83798393
ReactCurrentBatchConfig$2.transition = prevTransition;
83808394

@@ -8423,22 +8437,18 @@ function formStateReducer(oldState, newState) {
84238437

84248438
function mountFormState(action, initialStateProp, permalink) {
84258439
var initialState = initialStateProp;
8426-
8427-
var initialStateThenable = {
8428-
status: "fulfilled",
8429-
value: initialState,
8430-
then: function () {}
8431-
}; // State hook. The state is stored in a thenable which is then unwrapped by
84328440
// the `use` algorithm during render.
84338441

84348442
var stateHook = mountWorkInProgressHook();
8435-
stateHook.memoizedState = stateHook.baseState = initialStateThenable;
8443+
stateHook.memoizedState = stateHook.baseState = initialState; // TODO: Typing this "correctly" results in recursion limit errors
8444+
// const stateQueue: UpdateQueue<S | Awaited<S>, S | Awaited<S>> = {
8445+
84368446
var stateQueue = {
84378447
pending: null,
84388448
lanes: NoLanes,
84398449
dispatch: null,
84408450
lastRenderedReducer: formStateReducer,
8441-
lastRenderedState: initialStateThenable
8451+
lastRenderedState: initialState
84428452
};
84438453
stateHook.queue = stateQueue;
84448454
var setState = dispatchSetState.bind(
@@ -8492,9 +8502,14 @@ function updateFormStateImpl(
84928502
currentStateHook,
84938503
formStateReducer
84948504
),
8495-
thenable = _updateReducerImpl[0]; // This will suspend until the action finishes.
8496-
8497-
var state = useThenable(thenable);
8505+
actionResult = _updateReducerImpl[0]; // This will suspend until the action finishes.
8506+
8507+
var state =
8508+
typeof actionResult === "object" &&
8509+
actionResult !== null && // $FlowFixMe[method-unbinding]
8510+
typeof actionResult.then === "function"
8511+
? useThenable(actionResult)
8512+
: actionResult;
84988513
var actionQueueHook = updateWorkInProgressHook();
84998514
var actionQueue = actionQueueHook.queue;
85008515
var dispatch = actionQueue.dispatch; // Check if a new action was passed. If so, update it in an effect.
@@ -8534,8 +8549,7 @@ function rerenderFormState(action, initialState, permalink) {
85348549
return updateFormStateImpl(stateHook, currentStateHook, action);
85358550
} // This is a mount. No updates to process.
85368551

8537-
var thenable = stateHook.memoizedState;
8538-
var state = useThenable(thenable);
8552+
var state = stateHook.memoizedState;
85398553
var actionQueueHook = updateWorkInProgressHook();
85408554
var actionQueue = actionQueueHook.queue;
85418555
var dispatch = actionQueue.dispatch; // This may have changed during the rerender.
@@ -8980,12 +8994,12 @@ function startTransition(
89808994
// This is either `finishedState` or a thenable that resolves to
89818995
// `finishedState`, depending on whether we're inside an async
89828996
// action scope.
8983-
var _entangledResult = requestSyncActionContext(
8997+
var _entangledResult2 = requestSyncActionContext(
89848998
returnValue,
89858999
finishedState
89869000
);
89879001

8988-
dispatchSetState(fiber, queue, _entangledResult);
9002+
dispatchSetState(fiber, queue, _entangledResult2);
89899003
}
89909004
}
89919005
} catch (error) {
@@ -25267,7 +25281,7 @@ function createFiberRoot(
2526725281
return root;
2526825282
}
2526925283

25270-
var ReactVersion = "18.3.0-www-classic-5fd0277f";
25284+
var ReactVersion = "18.3.0-www-classic-846422de";
2527125285

2527225286
// Might add PROFILE later.
2527325287

0 commit comments

Comments
 (0)