Skip to content

Commit

Permalink
Capture React.startTransition errors and pass to reportError (#28111)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
acdlite committed Jan 26, 2024
1 parent c53cad6 commit c6f9c3a
Show file tree
Hide file tree
Showing 26 changed files with 464 additions and 314 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
763612647ceb66d95f728af896ca5e18a8181db8
60f190a55948a7512d4e2a336f03b45fd38d6a80
81 changes: 61 additions & 20 deletions compiled/facebook-www/React-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ if (__DEV__) {
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
}
var ReactVersion = "18.3.0-www-classic-a55e720b";
var ReactVersion = "18.3.0-www-classic-482168c3";

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

var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing;
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions;
// On WWW, false is used for a new modern build.

function getWrappedName(outerType, innerType, wrapperName) {
Expand Down Expand Up @@ -2984,32 +2985,72 @@ if (__DEV__) {
}
}

try {
var returnValue = scope();
callbacks.forEach(function (callback) {
return callback(currentTransition, returnValue);
});
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
if (enableAsyncActions) {
try {
var returnValue = scope();

{
if (prevTransition === null && currentTransition._updatedFibers) {
var updatedFibersCount = currentTransition._updatedFibers.size;
if (
typeof returnValue === "object" &&
returnValue !== null &&
typeof returnValue.then === "function"
) {
callbacks.forEach(function (callback) {
return callback(currentTransition, returnValue);
});
returnValue.then(noop, onError);
}
} catch (error) {
onError(error);
} finally {
warnAboutTransitionSubscriptions(prevTransition, currentTransition);
ReactCurrentBatchConfig.transition = prevTransition;
}
} else {
// When async actions are not enabled, startTransition does not
// capture errors.
try {
scope();
} finally {
warnAboutTransitionSubscriptions(prevTransition, currentTransition);
ReactCurrentBatchConfig.transition = prevTransition;
}
}
}

currentTransition._updatedFibers.clear();
function warnAboutTransitionSubscriptions(
prevTransition,
currentTransition
) {
{
if (prevTransition === null && currentTransition._updatedFibers) {
var updatedFibersCount = currentTransition._updatedFibers.size;

if (updatedFibersCount > 10) {
warn(
"Detected a large number of updates inside startTransition. " +
"If this is due to a subscription please re-write it to use React provided hooks. " +
"Otherwise concurrent mode guarantees are off the table."
);
}
currentTransition._updatedFibers.clear();

if (updatedFibersCount > 10) {
warn(
"Detected a large number of updates inside startTransition. " +
"If this is due to a subscription please re-write it to use React provided hooks. " +
"Otherwise concurrent mode guarantees are off the table."
);
}
}
}
}

function noop() {} // Use reportError, if it exists. Otherwise console.error. This is the same as
// the default for onRecoverableError.

var onError =
typeof reportError === "function" // In modern browsers, reportError will dispatch an error event,
? // emulating an uncaught JavaScript error.
reportError
: function (error) {
// In older browsers and test environments, fallback to console.error.
// eslint-disable-next-line react-internal/no-production-logging
console["error"](error);
};

var didWarnAboutMessageChannel = false;
var enqueueTaskImpl = null;
function enqueueTask(task) {
Expand Down
81 changes: 61 additions & 20 deletions compiled/facebook-www/React-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ if (__DEV__) {
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
}
var ReactVersion = "18.3.0-www-modern-c761065d";
var ReactVersion = "18.3.0-www-modern-2c842b55";

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

var enableDebugTracing = dynamicFeatureFlags.enableDebugTracing,
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing;
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions;
// On WWW, true is used for a new modern build.

function getWrappedName(outerType, innerType, wrapperName) {
Expand Down Expand Up @@ -2949,32 +2950,72 @@ if (__DEV__) {
}
}

try {
var returnValue = scope();
callbacks.forEach(function (callback) {
return callback(currentTransition, returnValue);
});
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
if (enableAsyncActions) {
try {
var returnValue = scope();

{
if (prevTransition === null && currentTransition._updatedFibers) {
var updatedFibersCount = currentTransition._updatedFibers.size;
if (
typeof returnValue === "object" &&
returnValue !== null &&
typeof returnValue.then === "function"
) {
callbacks.forEach(function (callback) {
return callback(currentTransition, returnValue);
});
returnValue.then(noop, onError);
}
} catch (error) {
onError(error);
} finally {
warnAboutTransitionSubscriptions(prevTransition, currentTransition);
ReactCurrentBatchConfig.transition = prevTransition;
}
} else {
// When async actions are not enabled, startTransition does not
// capture errors.
try {
scope();
} finally {
warnAboutTransitionSubscriptions(prevTransition, currentTransition);
ReactCurrentBatchConfig.transition = prevTransition;
}
}
}

currentTransition._updatedFibers.clear();
function warnAboutTransitionSubscriptions(
prevTransition,
currentTransition
) {
{
if (prevTransition === null && currentTransition._updatedFibers) {
var updatedFibersCount = currentTransition._updatedFibers.size;

if (updatedFibersCount > 10) {
warn(
"Detected a large number of updates inside startTransition. " +
"If this is due to a subscription please re-write it to use React provided hooks. " +
"Otherwise concurrent mode guarantees are off the table."
);
}
currentTransition._updatedFibers.clear();

if (updatedFibersCount > 10) {
warn(
"Detected a large number of updates inside startTransition. " +
"If this is due to a subscription please re-write it to use React provided hooks. " +
"Otherwise concurrent mode guarantees are off the table."
);
}
}
}
}

function noop() {} // Use reportError, if it exists. Otherwise console.error. This is the same as
// the default for onRecoverableError.

var onError =
typeof reportError === "function" // In modern browsers, reportError will dispatch an error event,
? // emulating an uncaught JavaScript error.
reportError
: function (error) {
// In older browsers and test environments, fallback to console.error.
// eslint-disable-next-line react-internal/no-production-logging
console["error"](error);
};

var didWarnAboutMessageChannel = false;
var enqueueTaskImpl = null;
function enqueueTask(task) {
Expand Down
45 changes: 33 additions & 12 deletions compiled/facebook-www/React-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ pureComponentPrototype.constructor = PureComponent;
assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = !0;
var isArrayImpl = Array.isArray,
enableTransitionTracing =
require("ReactFeatureFlags").enableTransitionTracing,
dynamicFeatureFlags = require("ReactFeatureFlags"),
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions,
hasOwnProperty = Object.prototype.hasOwnProperty,
ReactCurrentOwner$1 = { current: null },
RESERVED_PROPS$1 = { key: !0, ref: !0, __self: !0, __source: !0 };
Expand Down Expand Up @@ -280,7 +281,14 @@ function lazyInitializer(payload) {
if (1 === payload._status) return payload._result.default;
throw payload._result;
}
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner,
function noop() {}
var onError =
"function" === typeof reportError
? reportError
: function (error) {
console.error(error);
},
ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner,
RESERVED_PROPS = { key: !0, ref: !0, __self: !0, __source: !0 };
function jsx$1(type, config, maybeKey) {
var propName,
Expand Down Expand Up @@ -453,14 +461,27 @@ exports.startTransition = function (scope, options) {
void 0 !== options.name &&
((ReactCurrentBatchConfig.transition.name = options.name),
(ReactCurrentBatchConfig.transition.startTime = -1));
try {
var returnValue = scope();
callbacks.forEach(function (callback) {
return callback(currentTransition, returnValue);
});
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
if (enableAsyncActions)
try {
var returnValue = scope();
"object" === typeof returnValue &&
null !== returnValue &&
"function" === typeof returnValue.then &&
(callbacks.forEach(function (callback) {
return callback(currentTransition, returnValue);
}),
returnValue.then(noop, onError));
} catch (error) {
onError(error);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
else
try {
scope();
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
};
exports.unstable_Activity = REACT_OFFSCREEN_TYPE;
exports.unstable_Cache = REACT_CACHE_TYPE;
Expand Down Expand Up @@ -551,4 +572,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-www-classic-d6e83430";
exports.version = "18.3.0-www-classic-4ac70da3";
45 changes: 33 additions & 12 deletions compiled/facebook-www/React-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ pureComponentPrototype.constructor = PureComponent;
assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = !0;
var isArrayImpl = Array.isArray,
enableTransitionTracing =
require("ReactFeatureFlags").enableTransitionTracing,
dynamicFeatureFlags = require("ReactFeatureFlags"),
enableTransitionTracing = dynamicFeatureFlags.enableTransitionTracing,
enableAsyncActions = dynamicFeatureFlags.enableAsyncActions,
hasOwnProperty = Object.prototype.hasOwnProperty,
ReactCurrentOwner$1 = { current: null },
RESERVED_PROPS$1 = { key: !0, ref: !0, __self: !0, __source: !0 };
Expand Down Expand Up @@ -247,7 +248,14 @@ function lazyInitializer(payload) {
if (1 === payload._status) return payload._result.default;
throw payload._result;
}
var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner,
function noop() {}
var onError =
"function" === typeof reportError
? reportError
: function (error) {
console.error(error);
},
ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner,
RESERVED_PROPS = { key: !0, ref: !0, __self: !0, __source: !0 };
function jsx$1(type, config, maybeKey) {
var propName,
Expand Down Expand Up @@ -446,14 +454,27 @@ exports.startTransition = function (scope, options) {
void 0 !== options.name &&
((ReactCurrentBatchConfig.transition.name = options.name),
(ReactCurrentBatchConfig.transition.startTime = -1));
try {
var returnValue = scope();
callbacks.forEach(function (callback) {
return callback(currentTransition, returnValue);
});
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
if (enableAsyncActions)
try {
var returnValue = scope();
"object" === typeof returnValue &&
null !== returnValue &&
"function" === typeof returnValue.then &&
(callbacks.forEach(function (callback) {
return callback(currentTransition, returnValue);
}),
returnValue.then(noop, onError));
} catch (error) {
onError(error);
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
else
try {
scope();
} finally {
ReactCurrentBatchConfig.transition = prevTransition;
}
};
exports.unstable_Activity = REACT_OFFSCREEN_TYPE;
exports.unstable_Cache = REACT_CACHE_TYPE;
Expand Down Expand Up @@ -543,4 +564,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-www-modern-5a0946d0";
exports.version = "18.3.0-www-modern-f5a7ea87";
Loading

0 comments on commit c6f9c3a

Please sign in to comment.