Skip to content

Commit 27402d4

Browse files
committed
useActionState: On error, cancel remaining actions (#29695)
Based on - #29694 --- If an action in the useActionState queue errors, we shouldn't run any subsequent actions. The contract of useActionState is that the actions run in sequence, and that one action can assume that all previous actions have completed successfully. For example, in a shopping cart UI, you might dispatch an "Add to cart" action followed by a "Checkout" action. If the "Add to cart" action errors, the "Checkout" action should not run. An implication of this change is that once useActionState falls into an error state, the only way to recover is to reset the component tree, i.e. by unmounting and remounting. The way to customize the error handling behavior is to wrap the action body in a try/catch. DiffTrain build for [9598c41](9598c41)
1 parent a56f5cf commit 27402d4

34 files changed

+630
-570
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
67b05be0d216c4efebc4bb5acb12c861a18bd87c
1+
9598c41a20162c8a9d57ccf6a356aa183b00b61a
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
67b05be0d216c4efebc4bb5acb12c861a18bd87c
1+
9598c41a20162c8a9d57ccf6a356aa183b00b61a

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ if (
2222
) {
2323
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
2424
}
25-
var ReactVersion = '19.0.0-www-classic-67b05be0d2-20240603';
25+
var ReactVersion = '19.0.0-www-classic-9598c41a20-20240603';
2626

2727
// Re-export dynamic flags from the www version.
2828
var dynamicFeatureFlags = require('ReactFeatureFlags');

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ if (
2222
) {
2323
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
2424
}
25-
var ReactVersion = '19.0.0-www-modern-67b05be0d2-20240603';
25+
var ReactVersion = '19.0.0-www-modern-9598c41a20-20240603';
2626

2727
// Re-export dynamic flags from the www version.
2828
var dynamicFeatureFlags = require('ReactFeatureFlags');

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,4 +684,4 @@ exports.useSyncExternalStore = function (
684684
exports.useTransition = function () {
685685
return ReactSharedInternals.H.useTransition();
686686
};
687-
exports.version = "19.0.0-www-classic-67b05be0d2-20240603";
687+
exports.version = "19.0.0-www-classic-9598c41a20-20240603";

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -684,4 +684,4 @@ exports.useSyncExternalStore = function (
684684
exports.useTransition = function () {
685685
return ReactSharedInternals.H.useTransition();
686686
};
687-
exports.version = "19.0.0-www-modern-67b05be0d2-20240603";
687+
exports.version = "19.0.0-www-modern-9598c41a20-20240603";

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ exports.useSyncExternalStore = function (
688688
exports.useTransition = function () {
689689
return ReactSharedInternals.H.useTransition();
690690
};
691-
exports.version = "19.0.0-www-classic-67b05be0d2-20240603";
691+
exports.version = "19.0.0-www-classic-9598c41a20-20240603";
692692
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
693693
"function" ===
694694
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ exports.useSyncExternalStore = function (
688688
exports.useTransition = function () {
689689
return ReactSharedInternals.H.useTransition();
690690
};
691-
exports.version = "19.0.0-www-modern-67b05be0d2-20240603";
691+
exports.version = "19.0.0-www-modern-9598c41a20-20240603";
692692
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
693693
"function" ===
694694
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&

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

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function _assertThisInitialized(self) {
6060
return self;
6161
}
6262

63-
var ReactVersion = '19.0.0-www-classic-67b05be0d2-20240603';
63+
var ReactVersion = '19.0.0-www-classic-9598c41a20-20240603';
6464

6565
var LegacyRoot = 0;
6666
var ConcurrentRoot = 1;
@@ -9272,9 +9272,16 @@ function dispatchActionState(fiber, actionQueue, setPendingState, setState, payl
92729272
throw new Error('Cannot update form state while rendering.');
92739273
}
92749274

9275+
var currentAction = actionQueue.action;
9276+
9277+
if (currentAction === null) {
9278+
// An earlier action errored. Subsequent actions should not run.
9279+
return;
9280+
}
9281+
92759282
var actionNode = {
92769283
payload: payload,
9277-
action: actionQueue.action,
9284+
action: currentAction,
92789285
next: null,
92799286
// circular
92809287
isTransition: true,
@@ -9433,28 +9440,23 @@ function onActionSuccess(actionQueue, actionNode, nextState) {
94339440
}
94349441

94359442
function onActionError(actionQueue, actionNode, error) {
9436-
actionNode.status = 'rejected';
9437-
actionNode.reason = error;
9438-
notifyActionListeners(actionNode); // Pop the action from the queue and run the next pending action, if there
9439-
// are any.
9440-
// TODO: We should instead abort all the remaining actions in the queue.
9441-
9443+
// Mark all the following actions as rejected.
94429444
var last = actionQueue.pending;
9445+
actionQueue.pending = null;
94439446

94449447
if (last !== null) {
94459448
var first = last.next;
94469449

9447-
if (first === last) {
9448-
// This was the last action in the queue.
9449-
actionQueue.pending = null;
9450-
} else {
9451-
// Remove the first node from the circular queue.
9452-
var next = first.next;
9453-
last.next = next; // Run the next action.
9450+
do {
9451+
actionNode.status = 'rejected';
9452+
actionNode.reason = error;
9453+
notifyActionListeners(actionNode);
9454+
actionNode = actionNode.next;
9455+
} while (actionNode !== first);
9456+
} // Prevent subsequent actions from being dispatched.
94549457

9455-
runActionStateAction(actionQueue, next);
9456-
}
9457-
}
9458+
9459+
actionQueue.action = null;
94589460
}
94599461

94609462
function notifyActionListeners(actionNode) {

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

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ function _assertThisInitialized(self) {
6060
return self;
6161
}
6262

63-
var ReactVersion = '19.0.0-www-modern-67b05be0d2-20240603';
63+
var ReactVersion = '19.0.0-www-modern-9598c41a20-20240603';
6464

6565
var LegacyRoot = 0;
6666
var ConcurrentRoot = 1;
@@ -9061,9 +9061,16 @@ function dispatchActionState(fiber, actionQueue, setPendingState, setState, payl
90619061
throw new Error('Cannot update form state while rendering.');
90629062
}
90639063

9064+
var currentAction = actionQueue.action;
9065+
9066+
if (currentAction === null) {
9067+
// An earlier action errored. Subsequent actions should not run.
9068+
return;
9069+
}
9070+
90649071
var actionNode = {
90659072
payload: payload,
9066-
action: actionQueue.action,
9073+
action: currentAction,
90679074
next: null,
90689075
// circular
90699076
isTransition: true,
@@ -9222,28 +9229,23 @@ function onActionSuccess(actionQueue, actionNode, nextState) {
92229229
}
92239230

92249231
function onActionError(actionQueue, actionNode, error) {
9225-
actionNode.status = 'rejected';
9226-
actionNode.reason = error;
9227-
notifyActionListeners(actionNode); // Pop the action from the queue and run the next pending action, if there
9228-
// are any.
9229-
// TODO: We should instead abort all the remaining actions in the queue.
9230-
9232+
// Mark all the following actions as rejected.
92319233
var last = actionQueue.pending;
9234+
actionQueue.pending = null;
92329235

92339236
if (last !== null) {
92349237
var first = last.next;
92359238

9236-
if (first === last) {
9237-
// This was the last action in the queue.
9238-
actionQueue.pending = null;
9239-
} else {
9240-
// Remove the first node from the circular queue.
9241-
var next = first.next;
9242-
last.next = next; // Run the next action.
9239+
do {
9240+
actionNode.status = 'rejected';
9241+
actionNode.reason = error;
9242+
notifyActionListeners(actionNode);
9243+
actionNode = actionNode.next;
9244+
} while (actionNode !== first);
9245+
} // Prevent subsequent actions from being dispatched.
92439246

9244-
runActionStateAction(actionQueue, next);
9245-
}
9246-
}
9247+
9248+
actionQueue.action = null;
92479249
}
92489250

92499251
function notifyActionListeners(actionNode) {

0 commit comments

Comments
 (0)