Skip to content

Commit

Permalink
useActionState: On error, cancel remaining actions (#29695)
Browse files Browse the repository at this point in the history
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 commit 9598c41.
  • Loading branch information
acdlite committed Jun 3, 2024
1 parent bf759bd commit fe026dd
Show file tree
Hide file tree
Showing 14 changed files with 321 additions and 291 deletions.
2 changes: 1 addition & 1 deletion compiled-rn/VERSION_NATIVE_FB
Original file line number Diff line number Diff line change
@@ -1 +1 @@
19.0.0-native-fb-67b05be0d2-20240603
19.0.0-native-fb-9598c41a20-20240603
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<296ca7b82c0faacaf7aac3a6e16abd82>>
* @generated SignedSource<<a73da489ac04975cb709c8633eb35947>>
*/

'use strict';
Expand Down Expand Up @@ -8612,9 +8612,16 @@ function dispatchActionState(fiber, actionQueue, setPendingState, setState, payl
throw new Error('Cannot update form state while rendering.');
}

var currentAction = actionQueue.action;

if (currentAction === null) {
// An earlier action errored. Subsequent actions should not run.
return;
}

var actionNode = {
payload: payload,
action: actionQueue.action,
action: currentAction,
next: null,
// circular
isTransition: true,
Expand Down Expand Up @@ -8773,28 +8780,23 @@ function onActionSuccess(actionQueue, actionNode, nextState) {
}

function onActionError(actionQueue, actionNode, error) {
actionNode.status = 'rejected';
actionNode.reason = error;
notifyActionListeners(actionNode); // Pop the action from the queue and run the next pending action, if there
// are any.
// TODO: We should instead abort all the remaining actions in the queue.

// Mark all the following actions as rejected.
var last = actionQueue.pending;
actionQueue.pending = null;

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

if (first === last) {
// This was the last action in the queue.
actionQueue.pending = null;
} else {
// Remove the first node from the circular queue.
var next = first.next;
last.next = next; // Run the next action.
do {
actionNode.status = 'rejected';
actionNode.reason = error;
notifyActionListeners(actionNode);
actionNode = actionNode.next;
} while (actionNode !== first);
} // Prevent subsequent actions from being dispatched.

runActionStateAction(actionQueue, next);
}
}

actionQueue.action = null;
}

function notifyActionListeners(actionNode) {
Expand Down Expand Up @@ -23568,7 +23570,7 @@ identifierPrefix, onUncaughtError, onCaughtError, onRecoverableError, transition
return root;
}

var ReactVersion = '19.0.0-rc-67b05be0d2-20240603';
var ReactVersion = '19.0.0-rc-9598c41a20-20240603';

/*
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<8797f6b701596391f0ce6aaf8dbd0345>>
* @generated SignedSource<<c423cc156ab6bd82b7b2904bf63b4563>>
*/

"use strict";
Expand Down Expand Up @@ -2776,29 +2776,32 @@ function dispatchActionState(
) {
if (isRenderPhaseUpdate(fiber))
throw Error("Cannot update form state while rendering.");
var actionNode = {
payload: payload,
action: actionQueue.action,
next: null,
isTransition: !0,
status: "pending",
value: null,
reason: null,
listeners: [],
then: function (listener) {
actionNode.listeners.push(listener);
}
};
null !== ReactSharedInternals.T
? setPendingState(!0)
: (actionNode.isTransition = !1);
setState(actionNode);
fiber = actionQueue.pending;
null === fiber
? ((actionNode.next = actionQueue.pending = actionNode),
runActionStateAction(actionQueue, actionNode))
: ((actionNode.next = fiber.next),
(actionQueue.pending = fiber.next = actionNode));
fiber = actionQueue.action;
if (null !== fiber) {
var actionNode = {
payload: payload,
action: fiber,
next: null,
isTransition: !0,
status: "pending",
value: null,
reason: null,
listeners: [],
then: function (listener) {
actionNode.listeners.push(listener);
}
};
null !== ReactSharedInternals.T
? setPendingState(!0)
: (actionNode.isTransition = !1);
setState(actionNode);
setPendingState = actionQueue.pending;
null === setPendingState
? ((actionNode.next = actionQueue.pending = actionNode),
runActionStateAction(actionQueue, actionNode))
: ((actionNode.next = setPendingState.next),
(actionQueue.pending = setPendingState.next = actionNode));
}
}
function runActionStateAction(actionQueue, node) {
var action = node.action,
Expand Down Expand Up @@ -2856,17 +2859,18 @@ function onActionSuccess(actionQueue, actionNode, nextState) {
runActionStateAction(actionQueue, nextState)));
}
function onActionError(actionQueue, actionNode, error) {
actionNode.status = "rejected";
actionNode.reason = error;
notifyActionListeners(actionNode);
actionNode = actionQueue.pending;
null !== actionNode &&
((error = actionNode.next),
error === actionNode
? (actionQueue.pending = null)
: ((error = error.next),
(actionNode.next = error),
runActionStateAction(actionQueue, error)));
var last = actionQueue.pending;
actionQueue.pending = null;
if (null !== last) {
last = last.next;
do
(actionNode.status = "rejected"),
(actionNode.reason = error),
notifyActionListeners(actionNode),
(actionNode = actionNode.next);
while (actionNode !== last);
}
actionQueue.action = null;
}
function notifyActionListeners(actionNode) {
actionNode = actionNode.listeners;
Expand Down Expand Up @@ -9332,7 +9336,7 @@ var devToolsConfig$jscomp$inline_1048 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "19.0.0-rc-67b05be0d2-20240603",
version: "19.0.0-rc-9598c41a20-20240603",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1235 = {
Expand Down Expand Up @@ -9363,7 +9367,7 @@ var internals$jscomp$inline_1235 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-rc-67b05be0d2-20240603"
reconcilerVersion: "19.0.0-rc-9598c41a20-20240603"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1236 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<cc0f058c6b28d07a50a2d230bced40e3>>
* @generated SignedSource<<529a2966d841e9b9340a413ab80ac499>>
*/

"use strict";
Expand Down Expand Up @@ -2864,29 +2864,32 @@ function dispatchActionState(
) {
if (isRenderPhaseUpdate(fiber))
throw Error("Cannot update form state while rendering.");
var actionNode = {
payload: payload,
action: actionQueue.action,
next: null,
isTransition: !0,
status: "pending",
value: null,
reason: null,
listeners: [],
then: function (listener) {
actionNode.listeners.push(listener);
}
};
null !== ReactSharedInternals.T
? setPendingState(!0)
: (actionNode.isTransition = !1);
setState(actionNode);
fiber = actionQueue.pending;
null === fiber
? ((actionNode.next = actionQueue.pending = actionNode),
runActionStateAction(actionQueue, actionNode))
: ((actionNode.next = fiber.next),
(actionQueue.pending = fiber.next = actionNode));
fiber = actionQueue.action;
if (null !== fiber) {
var actionNode = {
payload: payload,
action: fiber,
next: null,
isTransition: !0,
status: "pending",
value: null,
reason: null,
listeners: [],
then: function (listener) {
actionNode.listeners.push(listener);
}
};
null !== ReactSharedInternals.T
? setPendingState(!0)
: (actionNode.isTransition = !1);
setState(actionNode);
setPendingState = actionQueue.pending;
null === setPendingState
? ((actionNode.next = actionQueue.pending = actionNode),
runActionStateAction(actionQueue, actionNode))
: ((actionNode.next = setPendingState.next),
(actionQueue.pending = setPendingState.next = actionNode));
}
}
function runActionStateAction(actionQueue, node) {
var action = node.action,
Expand Down Expand Up @@ -2944,17 +2947,18 @@ function onActionSuccess(actionQueue, actionNode, nextState) {
runActionStateAction(actionQueue, nextState)));
}
function onActionError(actionQueue, actionNode, error) {
actionNode.status = "rejected";
actionNode.reason = error;
notifyActionListeners(actionNode);
actionNode = actionQueue.pending;
null !== actionNode &&
((error = actionNode.next),
error === actionNode
? (actionQueue.pending = null)
: ((error = error.next),
(actionNode.next = error),
runActionStateAction(actionQueue, error)));
var last = actionQueue.pending;
actionQueue.pending = null;
if (null !== last) {
last = last.next;
do
(actionNode.status = "rejected"),
(actionNode.reason = error),
notifyActionListeners(actionNode),
(actionNode = actionNode.next);
while (actionNode !== last);
}
actionQueue.action = null;
}
function notifyActionListeners(actionNode) {
actionNode = actionNode.listeners;
Expand Down Expand Up @@ -9954,7 +9958,7 @@ var devToolsConfig$jscomp$inline_1131 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "19.0.0-rc-67b05be0d2-20240603",
version: "19.0.0-rc-9598c41a20-20240603",
rendererPackageName: "react-test-renderer"
};
(function (internals) {
Expand Down Expand Up @@ -9998,7 +10002,7 @@ var devToolsConfig$jscomp$inline_1131 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-rc-67b05be0d2-20240603"
reconcilerVersion: "19.0.0-rc-9598c41a20-20240603"
});
exports._Scheduler = Scheduler;
exports.act = act;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<e6d0112c6fab06e092aebff7ad288f57>>
* @generated SignedSource<<6742f2766c9c222c207784bf460af0f4>>
*/

'use strict';
Expand All @@ -24,7 +24,7 @@ if (
}
var dynamicFlagsUntyped = require('ReactNativeInternalFeatureFlags');

var ReactVersion = '19.0.0-rc-67b05be0d2-20240603';
var ReactVersion = '19.0.0-rc-9598c41a20-20240603';

// Re-export dynamic flags from the internal module.
var dynamicFlags = dynamicFlagsUntyped; // We destructure each value before re-exporting to avoid a dynamic look-up on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<390c3d9e32c6dd1322766c76c635606b>>
* @generated SignedSource<<3ee52e217ab8c6672129bc95c5f1f7af>>
*/

"use strict";
Expand Down Expand Up @@ -604,4 +604,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactSharedInternals.H.useTransition();
};
exports.version = "19.0.0-rc-67b05be0d2-20240603";
exports.version = "19.0.0-rc-9598c41a20-20240603";
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<9fd7a37ab321c8aef0e0e0ec19601330>>
* @generated SignedSource<<86e5c4b771984c5e7851b5bf56dd492f>>
*/

"use strict";
Expand Down Expand Up @@ -608,7 +608,7 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactSharedInternals.H.useTransition();
};
exports.version = "19.0.0-rc-67b05be0d2-20240603";
exports.version = "19.0.0-rc-9598c41a20-20240603";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
67b05be0d216c4efebc4bb5acb12c861a18bd87c
9598c41a20162c8a9d57ccf6a356aa183b00b61a
Loading

0 comments on commit fe026dd

Please sign in to comment.