Skip to content

Commit c6f9c3a

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 [60f190a](60f190a)
1 parent c53cad6 commit c6f9c3a

26 files changed

+464
-314
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
763612647ceb66d95f728af896ca5e18a8181db8
1+
60f190a55948a7512d4e2a336f03b45fd38d6a80

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

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ if (__DEV__) {
2424
) {
2525
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
2626
}
27-
var ReactVersion = "18.3.0-www-classic-a55e720b";
27+
var ReactVersion = "18.3.0-www-classic-482168c3";
2828

2929
// ATTENTION
3030
// When adding new symbols to this file,
@@ -472,7 +472,8 @@ if (__DEV__) {
472472
var dynamicFeatureFlags = require("ReactFeatureFlags");
473473

474474
var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
475-
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing;
475+
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
476+
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions;
476477
// On WWW, false is used for a new modern build.
477478

478479
function getWrappedName(outerType, innerType, wrapperName) {
@@ -2984,32 +2985,72 @@ if (__DEV__) {
29842985
}
29852986
}
29862987

2987-
try {
2988-
var returnValue = scope();
2989-
callbacks.forEach(function (callback) {
2990-
return callback(currentTransition, returnValue);
2991-
});
2992-
} finally {
2993-
ReactCurrentBatchConfig.transition = prevTransition;
2988+
if (enableAsyncActions) {
2989+
try {
2990+
var returnValue = scope();
29942991

2995-
{
2996-
if (prevTransition === null && currentTransition._updatedFibers) {
2997-
var updatedFibersCount = currentTransition._updatedFibers.size;
2992+
if (
2993+
typeof returnValue === "object" &&
2994+
returnValue !== null &&
2995+
typeof returnValue.then === "function"
2996+
) {
2997+
callbacks.forEach(function (callback) {
2998+
return callback(currentTransition, returnValue);
2999+
});
3000+
returnValue.then(noop, onError);
3001+
}
3002+
} catch (error) {
3003+
onError(error);
3004+
} finally {
3005+
warnAboutTransitionSubscriptions(prevTransition, currentTransition);
3006+
ReactCurrentBatchConfig.transition = prevTransition;
3007+
}
3008+
} else {
3009+
// When async actions are not enabled, startTransition does not
3010+
// capture errors.
3011+
try {
3012+
scope();
3013+
} finally {
3014+
warnAboutTransitionSubscriptions(prevTransition, currentTransition);
3015+
ReactCurrentBatchConfig.transition = prevTransition;
3016+
}
3017+
}
3018+
}
29983019

2999-
currentTransition._updatedFibers.clear();
3020+
function warnAboutTransitionSubscriptions(
3021+
prevTransition,
3022+
currentTransition
3023+
) {
3024+
{
3025+
if (prevTransition === null && currentTransition._updatedFibers) {
3026+
var updatedFibersCount = currentTransition._updatedFibers.size;
30003027

3001-
if (updatedFibersCount > 10) {
3002-
warn(
3003-
"Detected a large number of updates inside startTransition. " +
3004-
"If this is due to a subscription please re-write it to use React provided hooks. " +
3005-
"Otherwise concurrent mode guarantees are off the table."
3006-
);
3007-
}
3028+
currentTransition._updatedFibers.clear();
3029+
3030+
if (updatedFibersCount > 10) {
3031+
warn(
3032+
"Detected a large number of updates inside startTransition. " +
3033+
"If this is due to a subscription please re-write it to use React provided hooks. " +
3034+
"Otherwise concurrent mode guarantees are off the table."
3035+
);
30083036
}
30093037
}
30103038
}
30113039
}
30123040

3041+
function noop() {} // Use reportError, if it exists. Otherwise console.error. This is the same as
3042+
// the default for onRecoverableError.
3043+
3044+
var onError =
3045+
typeof reportError === "function" // In modern browsers, reportError will dispatch an error event,
3046+
? // emulating an uncaught JavaScript error.
3047+
reportError
3048+
: function (error) {
3049+
// In older browsers and test environments, fallback to console.error.
3050+
// eslint-disable-next-line react-internal/no-production-logging
3051+
console["error"](error);
3052+
};
3053+
30133054
var didWarnAboutMessageChannel = false;
30143055
var enqueueTaskImpl = null;
30153056
function enqueueTask(task) {

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

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ if (__DEV__) {
2424
) {
2525
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
2626
}
27-
var ReactVersion = "18.3.0-www-modern-c761065d";
27+
var ReactVersion = "18.3.0-www-modern-2c842b55";
2828

2929
// ATTENTION
3030
// When adding new symbols to this file,
@@ -472,7 +472,8 @@ if (__DEV__) {
472472
var dynamicFeatureFlags = require("ReactFeatureFlags");
473473

474474
var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
475-
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing;
475+
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
476+
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions;
476477
// On WWW, true is used for a new modern build.
477478

478479
function getWrappedName(outerType, innerType, wrapperName) {
@@ -2949,32 +2950,72 @@ if (__DEV__) {
29492950
}
29502951
}
29512952

2952-
try {
2953-
var returnValue = scope();
2954-
callbacks.forEach(function (callback) {
2955-
return callback(currentTransition, returnValue);
2956-
});
2957-
} finally {
2958-
ReactCurrentBatchConfig.transition = prevTransition;
2953+
if (enableAsyncActions) {
2954+
try {
2955+
var returnValue = scope();
29592956

2960-
{
2961-
if (prevTransition === null && currentTransition._updatedFibers) {
2962-
var updatedFibersCount = currentTransition._updatedFibers.size;
2957+
if (
2958+
typeof returnValue === "object" &&
2959+
returnValue !== null &&
2960+
typeof returnValue.then === "function"
2961+
) {
2962+
callbacks.forEach(function (callback) {
2963+
return callback(currentTransition, returnValue);
2964+
});
2965+
returnValue.then(noop, onError);
2966+
}
2967+
} catch (error) {
2968+
onError(error);
2969+
} finally {
2970+
warnAboutTransitionSubscriptions(prevTransition, currentTransition);
2971+
ReactCurrentBatchConfig.transition = prevTransition;
2972+
}
2973+
} else {
2974+
// When async actions are not enabled, startTransition does not
2975+
// capture errors.
2976+
try {
2977+
scope();
2978+
} finally {
2979+
warnAboutTransitionSubscriptions(prevTransition, currentTransition);
2980+
ReactCurrentBatchConfig.transition = prevTransition;
2981+
}
2982+
}
2983+
}
29632984

2964-
currentTransition._updatedFibers.clear();
2985+
function warnAboutTransitionSubscriptions(
2986+
prevTransition,
2987+
currentTransition
2988+
) {
2989+
{
2990+
if (prevTransition === null && currentTransition._updatedFibers) {
2991+
var updatedFibersCount = currentTransition._updatedFibers.size;
29652992

2966-
if (updatedFibersCount > 10) {
2967-
warn(
2968-
"Detected a large number of updates inside startTransition. " +
2969-
"If this is due to a subscription please re-write it to use React provided hooks. " +
2970-
"Otherwise concurrent mode guarantees are off the table."
2971-
);
2972-
}
2993+
currentTransition._updatedFibers.clear();
2994+
2995+
if (updatedFibersCount > 10) {
2996+
warn(
2997+
"Detected a large number of updates inside startTransition. " +
2998+
"If this is due to a subscription please re-write it to use React provided hooks. " +
2999+
"Otherwise concurrent mode guarantees are off the table."
3000+
);
29733001
}
29743002
}
29753003
}
29763004
}
29773005

3006+
function noop() {} // Use reportError, if it exists. Otherwise console.error. This is the same as
3007+
// the default for onRecoverableError.
3008+
3009+
var onError =
3010+
typeof reportError === "function" // In modern browsers, reportError will dispatch an error event,
3011+
? // emulating an uncaught JavaScript error.
3012+
reportError
3013+
: function (error) {
3014+
// In older browsers and test environments, fallback to console.error.
3015+
// eslint-disable-next-line react-internal/no-production-logging
3016+
console["error"](error);
3017+
};
3018+
29783019
var didWarnAboutMessageChannel = false;
29793020
var enqueueTaskImpl = null;
29803021
function enqueueTask(task) {

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

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,9 @@ pureComponentPrototype.constructor = PureComponent;
8181
assign(pureComponentPrototype, Component.prototype);
8282
pureComponentPrototype.isPureReactComponent = !0;
8383
var isArrayImpl = Array.isArray,
84-
enableTransitionTracing =
85-
require("ReactFeatureFlags").enableTransitionTracing,
84+
dynamicFeatureFlags = require("ReactFeatureFlags"),
85+
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
86+
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions,
8687
hasOwnProperty = Object.prototype.hasOwnProperty,
8788
ReactCurrentOwner$1 = { current: null },
8889
RESERVED_PROPS$1 = { key: !0, ref: !0, __self: !0, __source: !0 };
@@ -280,7 +281,14 @@ function lazyInitializer(payload) {
280281
if (1 === payload._status) return payload._result.default;
281282
throw payload._result;
282283
}
283-
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner,
284+
function noop() {}
285+
var onError =
286+
"function" === typeof reportError
287+
? reportError
288+
: function (error) {
289+
console.error(error);
290+
},
291+
ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner,
284292
RESERVED_PROPS = { key: !0, ref: !0, __self: !0, __source: !0 };
285293
function jsx$1(type, config, maybeKey) {
286294
var propName,
@@ -453,14 +461,27 @@ exports.startTransition = function (scope, options) {
453461
void 0 !== options.name &&
454462
((ReactCurrentBatchConfig.transition.name = options.name),
455463
(ReactCurrentBatchConfig.transition.startTime = -1));
456-
try {
457-
var returnValue = scope();
458-
callbacks.forEach(function (callback) {
459-
return callback(currentTransition, returnValue);
460-
});
461-
} finally {
462-
ReactCurrentBatchConfig.transition = prevTransition;
463-
}
464+
if (enableAsyncActions)
465+
try {
466+
var returnValue = scope();
467+
"object" === typeof returnValue &&
468+
null !== returnValue &&
469+
"function" === typeof returnValue.then &&
470+
(callbacks.forEach(function (callback) {
471+
return callback(currentTransition, returnValue);
472+
}),
473+
returnValue.then(noop, onError));
474+
} catch (error) {
475+
onError(error);
476+
} finally {
477+
ReactCurrentBatchConfig.transition = prevTransition;
478+
}
479+
else
480+
try {
481+
scope();
482+
} finally {
483+
ReactCurrentBatchConfig.transition = prevTransition;
484+
}
464485
};
465486
exports.unstable_Activity = REACT_OFFSCREEN_TYPE;
466487
exports.unstable_Cache = REACT_CACHE_TYPE;
@@ -551,4 +572,4 @@ exports.useSyncExternalStore = function (
551572
exports.useTransition = function () {
552573
return ReactCurrentDispatcher.current.useTransition();
553574
};
554-
exports.version = "18.3.0-www-classic-d6e83430";
575+
exports.version = "18.3.0-www-classic-4ac70da3";

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

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ pureComponentPrototype.constructor = PureComponent;
8080
assign(pureComponentPrototype, Component.prototype);
8181
pureComponentPrototype.isPureReactComponent = !0;
8282
var isArrayImpl = Array.isArray,
83-
enableTransitionTracing =
84-
require("ReactFeatureFlags").enableTransitionTracing,
83+
dynamicFeatureFlags = require("ReactFeatureFlags"),
84+
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
85+
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions,
8586
hasOwnProperty = Object.prototype.hasOwnProperty,
8687
ReactCurrentOwner$1 = { current: null },
8788
RESERVED_PROPS$1 = { key: !0, ref: !0, __self: !0, __source: !0 };
@@ -247,7 +248,14 @@ function lazyInitializer(payload) {
247248
if (1 === payload._status) return payload._result.default;
248249
throw payload._result;
249250
}
250-
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner,
251+
function noop() {}
252+
var onError =
253+
"function" === typeof reportError
254+
? reportError
255+
: function (error) {
256+
console.error(error);
257+
},
258+
ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner,
251259
RESERVED_PROPS = { key: !0, ref: !0, __self: !0, __source: !0 };
252260
function jsx$1(type, config, maybeKey) {
253261
var propName,
@@ -446,14 +454,27 @@ exports.startTransition = function (scope, options) {
446454
void 0 !== options.name &&
447455
((ReactCurrentBatchConfig.transition.name = options.name),
448456
(ReactCurrentBatchConfig.transition.startTime = -1));
449-
try {
450-
var returnValue = scope();
451-
callbacks.forEach(function (callback) {
452-
return callback(currentTransition, returnValue);
453-
});
454-
} finally {
455-
ReactCurrentBatchConfig.transition = prevTransition;
456-
}
457+
if (enableAsyncActions)
458+
try {
459+
var returnValue = scope();
460+
"object" === typeof returnValue &&
461+
null !== returnValue &&
462+
"function" === typeof returnValue.then &&
463+
(callbacks.forEach(function (callback) {
464+
return callback(currentTransition, returnValue);
465+
}),
466+
returnValue.then(noop, onError));
467+
} catch (error) {
468+
onError(error);
469+
} finally {
470+
ReactCurrentBatchConfig.transition = prevTransition;
471+
}
472+
else
473+
try {
474+
scope();
475+
} finally {
476+
ReactCurrentBatchConfig.transition = prevTransition;
477+
}
457478
};
458479
exports.unstable_Activity = REACT_OFFSCREEN_TYPE;
459480
exports.unstable_Cache = REACT_CACHE_TYPE;
@@ -543,4 +564,4 @@ exports.useSyncExternalStore = function (
543564
exports.useTransition = function () {
544565
return ReactCurrentDispatcher.current.useTransition();
545566
};
546-
exports.version = "18.3.0-www-modern-5a0946d0";
567+
exports.version = "18.3.0-www-modern-f5a7ea87";

0 commit comments

Comments
 (0)