Skip to content

Commit 39a042c

Browse files
committed
Capture React.startTransition errors and pass to reportError (#28111)
To make React.startTransition more consistent with the hook form of startTransition, we capture errors thrown by the scope function and pass them to the global reportError function. (This is also what we do as a default for onRecoverableError.) This is a breaking change because it means that errors inside of startTransition will no longer bubble up to the caller. You can still catch the error by putting a try/catch block inside of the scope function itself. We do the same for async actions to prevent "unhandled promise rejection" warnings. The motivation is to avoid a refactor hazard when changing from a sync to an async action, or from useTransition to startTransition. DiffTrain build for commit 60f190a.
1 parent ef45401 commit 39a042c

File tree

13 files changed

+114
-106
lines changed

13 files changed

+114
-106
lines changed

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

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<44707665e2d49df89524e6821112ab6c>>
10+
* @generated SignedSource<<202c1c7cbff4135eff9604c4cb02a215>>
1111
*/
1212

1313
"use strict";
@@ -8307,14 +8307,14 @@ if (__DEV__) {
83078307

83088308
try {
83098309
var returnValue = action(prevState, payload);
8310-
notifyTransitionCallbacks(currentTransition, returnValue);
83118310

83128311
if (
83138312
returnValue !== null &&
83148313
typeof returnValue === "object" && // $FlowFixMe[method-unbinding]
83158314
typeof returnValue.then === "function"
83168315
) {
8317-
var thenable = returnValue; // Attach a listener to read the return state of the action. As soon as
8316+
var thenable = returnValue;
8317+
notifyTransitionCallbacks(currentTransition, thenable); // Attach a listener to read the return state of the action. As soon as
83188318
// this resolves, we can run the next action in the sequence.
83198319

83208320
thenable.then(
@@ -8907,8 +8907,7 @@ if (__DEV__) {
89078907

89088908
try {
89098909
if (enableAsyncActions) {
8910-
var returnValue = callback();
8911-
notifyTransitionCallbacks(currentTransition, returnValue); // Check if we're inside an async action scope. If so, we'll entangle
8910+
var returnValue = callback(); // Check if we're inside an async action scope. If so, we'll entangle
89128911
// this new action with the existing scope.
89138912
//
89148913
// If we're not already inside an async action scope, and this action is
@@ -8922,7 +8921,8 @@ if (__DEV__) {
89228921
typeof returnValue === "object" &&
89238922
typeof returnValue.then === "function"
89248923
) {
8925-
var thenable = returnValue; // Create a thenable that resolves to `finishedState` once the async
8924+
var thenable = returnValue;
8925+
notifyTransitionCallbacks(currentTransition, thenable); // Create a thenable that resolves to `finishedState` once the async
89268926
// action has completed.
89278927

89288928
var thenableForFinishedState = chainThenableValue(
@@ -16288,20 +16288,15 @@ if (__DEV__) {
1628816288
if (transition !== null) {
1628916289
// Whenever a transition update is scheduled, register a callback on the
1629016290
// transition object so we can get the return value of the scope function.
16291-
transition._callbacks.add(handleTransitionScopeResult);
16291+
transition._callbacks.add(handleAsyncAction);
1629216292
}
1629316293

1629416294
return transition;
1629516295
}
1629616296

16297-
function handleTransitionScopeResult(transition, returnValue) {
16298-
if (
16299-
returnValue !== null &&
16300-
typeof returnValue === "object" &&
16301-
typeof returnValue.then === "function"
16302-
) {
16297+
function handleAsyncAction(transition, thenable) {
16298+
{
1630316299
// This is an async action.
16304-
var thenable = returnValue;
1630516300
entangleAsyncAction(transition, thenable);
1630616301
}
1630716302
}
@@ -25612,7 +25607,7 @@ if (__DEV__) {
2561225607
return root;
2561325608
}
2561425609

25615-
var ReactVersion = "18.3.0-canary-51c380d6e-20240126";
25610+
var ReactVersion = "18.3.0-canary-60f190a55-20240126";
2561625611

2561725612
// Might add PROFILE later.
2561825613

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<1eb4050564e1744e070d195be2dae5ee>>
10+
* @generated SignedSource<<876c7c325bf8c60091b5193da65519e1>>
1111
*/
1212

1313
"use strict";
@@ -2752,11 +2752,11 @@ function runFormStateAction(actionQueue, setState, payload) {
27522752
ReactCurrentBatchConfig$2.transition = currentTransition;
27532753
try {
27542754
var returnValue = action(prevState, payload);
2755-
notifyTransitionCallbacks(currentTransition, returnValue);
27562755
null !== returnValue &&
27572756
"object" === typeof returnValue &&
27582757
"function" === typeof returnValue.then
2759-
? (returnValue.then(
2758+
? (notifyTransitionCallbacks(currentTransition, returnValue),
2759+
returnValue.then(
27602760
function (nextState) {
27612761
actionQueue.state = nextState;
27622762
finishRunningFormStateAction(actionQueue, setState);
@@ -2945,12 +2945,12 @@ function startTransition(fiber, queue, pendingState, finishedState, callback) {
29452945
dispatchOptimisticSetState(fiber, !1, queue, pendingState);
29462946
try {
29472947
var returnValue = callback();
2948-
notifyTransitionCallbacks(currentTransition, returnValue);
29492948
if (
29502949
null !== returnValue &&
29512950
"object" === typeof returnValue &&
29522951
"function" === typeof returnValue.then
29532952
) {
2953+
notifyTransitionCallbacks(currentTransition, returnValue);
29542954
var thenableForFinishedState = chainThenableValue(
29552955
returnValue,
29562956
finishedState
@@ -5058,14 +5058,11 @@ function releaseCache(cache) {
50585058
var ReactCurrentBatchConfig$1 = ReactSharedInternals.ReactCurrentBatchConfig;
50595059
function requestCurrentTransition() {
50605060
var transition = ReactCurrentBatchConfig$1.transition;
5061-
null !== transition && transition._callbacks.add(handleTransitionScopeResult);
5061+
null !== transition && transition._callbacks.add(handleAsyncAction);
50625062
return transition;
50635063
}
5064-
function handleTransitionScopeResult(transition, returnValue) {
5065-
null !== returnValue &&
5066-
"object" === typeof returnValue &&
5067-
"function" === typeof returnValue.then &&
5068-
entangleAsyncAction(transition, returnValue);
5064+
function handleAsyncAction(transition, thenable) {
5065+
entangleAsyncAction(transition, thenable);
50695066
}
50705067
function notifyTransitionCallbacks(transition, returnValue) {
50715068
transition._callbacks.forEach(function (callback) {
@@ -9152,7 +9149,7 @@ var devToolsConfig$jscomp$inline_1029 = {
91529149
throw Error("TestRenderer does not support findFiberByHostInstance()");
91539150
},
91549151
bundleType: 0,
9155-
version: "18.3.0-canary-51c380d6e-20240126",
9152+
version: "18.3.0-canary-60f190a55-20240126",
91569153
rendererPackageName: "react-test-renderer"
91579154
};
91589155
var internals$jscomp$inline_1205 = {
@@ -9183,7 +9180,7 @@ var internals$jscomp$inline_1205 = {
91839180
scheduleRoot: null,
91849181
setRefreshHandler: null,
91859182
getCurrentFiber: null,
9186-
reconcilerVersion: "18.3.0-canary-51c380d6e-20240126"
9183+
reconcilerVersion: "18.3.0-canary-60f190a55-20240126"
91879184
};
91889185
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
91899186
var hook$jscomp$inline_1206 = __REACT_DEVTOOLS_GLOBAL_HOOK__;

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<b00845aa1dee4858d90d0838346bc576>>
10+
* @generated SignedSource<<409ac0ee00a9c6d06fcdff021f44a0bf>>
1111
*/
1212

1313
"use strict";
@@ -2772,11 +2772,11 @@ function runFormStateAction(actionQueue, setState, payload) {
27722772
ReactCurrentBatchConfig$2.transition = currentTransition;
27732773
try {
27742774
var returnValue = action(prevState, payload);
2775-
notifyTransitionCallbacks(currentTransition, returnValue);
27762775
null !== returnValue &&
27772776
"object" === typeof returnValue &&
27782777
"function" === typeof returnValue.then
2779-
? (returnValue.then(
2778+
? (notifyTransitionCallbacks(currentTransition, returnValue),
2779+
returnValue.then(
27802780
function (nextState) {
27812781
actionQueue.state = nextState;
27822782
finishRunningFormStateAction(actionQueue, setState);
@@ -2965,12 +2965,12 @@ function startTransition(fiber, queue, pendingState, finishedState, callback) {
29652965
dispatchOptimisticSetState(fiber, !1, queue, pendingState);
29662966
try {
29672967
var returnValue = callback();
2968-
notifyTransitionCallbacks(currentTransition, returnValue);
29692968
if (
29702969
null !== returnValue &&
29712970
"object" === typeof returnValue &&
29722971
"function" === typeof returnValue.then
29732972
) {
2973+
notifyTransitionCallbacks(currentTransition, returnValue);
29742974
var thenableForFinishedState = chainThenableValue(
29752975
returnValue,
29762976
finishedState
@@ -5164,14 +5164,11 @@ function releaseCache(cache) {
51645164
var ReactCurrentBatchConfig$1 = ReactSharedInternals.ReactCurrentBatchConfig;
51655165
function requestCurrentTransition() {
51665166
var transition = ReactCurrentBatchConfig$1.transition;
5167-
null !== transition && transition._callbacks.add(handleTransitionScopeResult);
5167+
null !== transition && transition._callbacks.add(handleAsyncAction);
51685168
return transition;
51695169
}
5170-
function handleTransitionScopeResult(transition, returnValue) {
5171-
null !== returnValue &&
5172-
"object" === typeof returnValue &&
5173-
"function" === typeof returnValue.then &&
5174-
entangleAsyncAction(transition, returnValue);
5170+
function handleAsyncAction(transition, thenable) {
5171+
entangleAsyncAction(transition, thenable);
51755172
}
51765173
function notifyTransitionCallbacks(transition, returnValue) {
51775174
transition._callbacks.forEach(function (callback) {
@@ -9580,7 +9577,7 @@ var devToolsConfig$jscomp$inline_1071 = {
95809577
throw Error("TestRenderer does not support findFiberByHostInstance()");
95819578
},
95829579
bundleType: 0,
9583-
version: "18.3.0-canary-51c380d6e-20240126",
9580+
version: "18.3.0-canary-60f190a55-20240126",
95849581
rendererPackageName: "react-test-renderer"
95859582
};
95869583
var internals$jscomp$inline_1246 = {
@@ -9611,7 +9608,7 @@ var internals$jscomp$inline_1246 = {
96119608
scheduleRoot: null,
96129609
setRefreshHandler: null,
96139610
getCurrentFiber: null,
9614-
reconcilerVersion: "18.3.0-canary-51c380d6e-20240126"
9611+
reconcilerVersion: "18.3.0-canary-60f190a55-20240126"
96159612
};
96169613
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
96179614
var hook$jscomp$inline_1247 = __REACT_DEVTOOLS_GLOBAL_HOOK__;

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/cjs/React-dev.js

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<6a375816771645c6a6298e391b95480f>>
10+
* @generated SignedSource<<c41561dadb43d7158811daa4e8000813>>
1111
*/
1212

1313
"use strict";
@@ -24,7 +24,7 @@ if (__DEV__) {
2424
) {
2525
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
2626
}
27-
var ReactVersion = "18.3.0-canary-51c380d6e-20240126";
27+
var ReactVersion = "18.3.0-canary-60f190a55-20240126";
2828

2929
// ATTENTION
3030
// When adding new symbols to this file,
@@ -2650,31 +2650,48 @@ if (__DEV__) {
26502650
ReactCurrentBatchConfig.transition._updatedFibers = new Set();
26512651
}
26522652

2653-
try {
2654-
var returnValue = scope();
2655-
callbacks.forEach(function (callback) {
2656-
return callback(currentTransition, returnValue);
2657-
});
2658-
} finally {
2659-
ReactCurrentBatchConfig.transition = prevTransition;
2653+
{
2654+
// When async actions are not enabled, startTransition does not
2655+
// capture errors.
2656+
try {
2657+
scope();
2658+
} finally {
2659+
warnAboutTransitionSubscriptions(prevTransition, currentTransition);
2660+
ReactCurrentBatchConfig.transition = prevTransition;
2661+
}
2662+
}
2663+
}
26602664

2661-
{
2662-
if (prevTransition === null && currentTransition._updatedFibers) {
2663-
var updatedFibersCount = currentTransition._updatedFibers.size;
2665+
function warnAboutTransitionSubscriptions(
2666+
prevTransition,
2667+
currentTransition
2668+
) {
2669+
{
2670+
if (prevTransition === null && currentTransition._updatedFibers) {
2671+
var updatedFibersCount = currentTransition._updatedFibers.size;
26642672

2665-
currentTransition._updatedFibers.clear();
2673+
currentTransition._updatedFibers.clear();
26662674

2667-
if (updatedFibersCount > 10) {
2668-
warn(
2669-
"Detected a large number of updates inside startTransition. " +
2670-
"If this is due to a subscription please re-write it to use React provided hooks. " +
2671-
"Otherwise concurrent mode guarantees are off the table."
2672-
);
2673-
}
2675+
if (updatedFibersCount > 10) {
2676+
warn(
2677+
"Detected a large number of updates inside startTransition. " +
2678+
"If this is due to a subscription please re-write it to use React provided hooks. " +
2679+
"Otherwise concurrent mode guarantees are off the table."
2680+
);
26742681
}
26752682
}
26762683
}
26772684
}
2685+
// the default for onRecoverableError.
2686+
2687+
typeof reportError === "function" // In modern browsers, reportError will dispatch an error event,
2688+
? // emulating an uncaught JavaScript error.
2689+
reportError
2690+
: function (error) {
2691+
// In older browsers and test environments, fallback to console.error.
2692+
// eslint-disable-next-line react-internal/no-production-logging
2693+
console["error"](error);
2694+
};
26782695

26792696
var didWarnAboutMessageChannel = false;
26802697
var enqueueTaskImpl = null;

compiled-rn/facebook-fbsource/xplat/js/RKJSModules/vendor/react/cjs/React-prod.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* @noflow
88
* @nolint
99
* @preventMunge
10-
* @generated SignedSource<<3be9c2392f3e2ba0d334baa46208c093>>
10+
* @generated SignedSource<<01a7046607ddd10a45604fb003900a70>>
1111
*/
1212

1313
"use strict";
@@ -278,6 +278,11 @@ function lazyInitializer(payload) {
278278
if (1 === payload._status) return payload._result.default;
279279
throw payload._result;
280280
}
281+
"function" === typeof reportError
282+
? reportError
283+
: function (error) {
284+
console.error(error);
285+
};
281286
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner,
282287
RESERVED_PROPS = { key: !0, ref: !0, __self: !0, __source: !0 };
283288
function jsx$1(type, config, maybeKey) {
@@ -443,14 +448,10 @@ exports.memo = function (type, compare) {
443448
};
444449
exports.startTransition = function (scope) {
445450
var prevTransition = ReactCurrentBatchConfig.transition,
446-
callbacks = new Set();
447-
ReactCurrentBatchConfig.transition = { _callbacks: callbacks };
448-
var currentTransition = ReactCurrentBatchConfig.transition;
451+
transition = { _callbacks: new Set() };
452+
ReactCurrentBatchConfig.transition = transition;
449453
try {
450-
var returnValue = scope();
451-
callbacks.forEach(function (callback) {
452-
return callback(currentTransition, returnValue);
453-
});
454+
scope();
454455
} finally {
455456
ReactCurrentBatchConfig.transition = prevTransition;
456457
}
@@ -544,4 +545,4 @@ exports.useSyncExternalStore = function (
544545
exports.useTransition = function () {
545546
return ReactCurrentDispatcher.current.useTransition();
546547
};
547-
exports.version = "18.3.0-canary-51c380d6e-20240126";
548+
exports.version = "18.3.0-canary-60f190a55-20240126";

0 commit comments

Comments
 (0)