Fix: Suspend while recovering from hydration error (#28800)

Fixes a bug that happens when an error occurs during hydration, React
switches to client rendering, and then the client render suspends. It
works correctly if there's a Suspense boundary on the stack, but not if
it happens in the shell of the app.

Prior to this fix, the app would crash with an "Unknown root exit
status" error.

I left a TODO comment for how we might refactor this code to be less
confusing in the future.

DiffTrain build for commit https://github.com/facebook/react/commit/3f9e237a2feb74f1fca23b76d9d2e9e1713e2ba1.
This commit is contained in:
acdlite
2024-04-09 21:16:18 +00:00
parent d0f92146e2
commit d2b16516a1
10 changed files with 112 additions and 49 deletions
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<fdd11cfd0a9f56383a8f5fff2c50fc9e>>
* @generated SignedSource<<881e78352310ebeea1f473a89441e0c9>>
*/
"use strict";
@@ -22773,20 +22773,31 @@ if (__DEV__) {
} // Check if something threw
if (exitStatus === RootErrored) {
var originallyAttemptedLanes = lanes;
var lanesThatJustErrored = lanes;
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
originallyAttemptedLanes
lanesThatJustErrored
);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(
root,
originallyAttemptedLanes,
lanesThatJustErrored,
errorRetryLanes
);
renderWasConcurrent = false;
renderWasConcurrent = false; // Need to check the exit status again.
if (exitStatus !== RootErrored) {
// The root did not error this time. Restart the exit algorithm
// from the beginning.
// TODO: Refactor the exit algorithm to be less confusing. Maybe
// more branches + recursion instead of a loop. I think the only
// thing that causes it to be a loop is the RootDidNotComplete
// check. If that's true, then we don't need a loop/recursion
// at all.
continue;
}
}
}
@@ -26590,7 +26601,7 @@ if (__DEV__) {
return root;
}
var ReactVersion = "19.0.0-canary-27752dcf";
var ReactVersion = "19.0.0-canary-89546c49";
/*
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<761db320e8f45126e2feb86ac8bca704>>
* @generated SignedSource<<5159750d4ca65c393d679d370a71d7cb>>
*/
"use strict";
@@ -7472,13 +7472,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
@@ -9119,7 +9124,7 @@ var devToolsConfig$jscomp$inline_1019 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "19.0.0-canary-3d34fdc4",
version: "19.0.0-canary-df9de31a",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1238 = {
@@ -9150,7 +9155,7 @@ var internals$jscomp$inline_1238 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-3d34fdc4"
reconcilerVersion: "19.0.0-canary-df9de31a"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1239 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<ec92ba0d6674d69f102b6b07bcc607e9>>
* @generated SignedSource<<21f520424802b9edb8fc0a8c96178e1c>>
*/
"use strict";
@@ -7966,13 +7966,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
@@ -9757,7 +9762,7 @@ var devToolsConfig$jscomp$inline_1082 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "19.0.0-canary-09ccb82c",
version: "19.0.0-canary-12ee58d1",
rendererPackageName: "react-test-renderer"
};
(function (internals) {
@@ -9801,7 +9806,7 @@ var devToolsConfig$jscomp$inline_1082 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-09ccb82c"
reconcilerVersion: "19.0.0-canary-12ee58d1"
});
exports._Scheduler = Scheduler;
exports.act = act;
@@ -1 +1 @@
64c8d2d45d49dbb2f59ea23e5e739eb79e124abc
3f9e237a2feb74f1fca23b76d9d2e9e1713e2ba1
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<494d27c89fdf2fb47e01cb552bf35a07>>
* @generated SignedSource<<a4571cdfd524cd72b685d2a3adeafd2c>>
*/
"use strict";
@@ -26242,20 +26242,31 @@ to return true:wantsResponderID| |
} // Check if something threw
if (exitStatus === RootErrored) {
var originallyAttemptedLanes = lanes;
var lanesThatJustErrored = lanes;
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
originallyAttemptedLanes
lanesThatJustErrored
);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(
root,
originallyAttemptedLanes,
lanesThatJustErrored,
errorRetryLanes
);
renderWasConcurrent = false;
renderWasConcurrent = false; // Need to check the exit status again.
if (exitStatus !== RootErrored) {
// The root did not error this time. Restart the exit algorithm
// from the beginning.
// TODO: Refactor the exit algorithm to be less confusing. Maybe
// more branches + recursion instead of a loop. I think the only
// thing that causes it to be a loop is the RootDidNotComplete
// check. If that's true, then we don't need a loop/recursion
// at all.
continue;
}
}
}
@@ -30237,7 +30248,7 @@ to return true:wantsResponderID| |
return root;
}
var ReactVersion = "19.0.0-canary-d3b8f5e2";
var ReactVersion = "19.0.0-canary-704f6d1b";
/*
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<16efbcb0da2cbd078a40efb1a0552fe8>>
* @generated SignedSource<<2e33e857cb3eee98d5e57d1509811f8c>>
*/
"use strict";
@@ -9055,13 +9055,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
@@ -10572,7 +10577,7 @@ var roots = new Map(),
devToolsConfig$jscomp$inline_1099 = {
findFiberByHostInstance: getInstanceFromNode,
bundleType: 0,
version: "19.0.0-canary-120922b5",
version: "19.0.0-canary-a19c8da4",
rendererPackageName: "react-native-renderer",
rendererConfig: {
getInspectorDataForInstance: getInspectorDataForInstance,
@@ -10615,7 +10620,7 @@ var internals$jscomp$inline_1366 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-120922b5"
reconcilerVersion: "19.0.0-canary-a19c8da4"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1367 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<a3c25968a30f5f8cd268a3314ac130fe>>
* @generated SignedSource<<5fa81808919e6dc188f7876d0baea787>>
*/
"use strict";
@@ -9585,13 +9585,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
@@ -11277,7 +11282,7 @@ var roots = new Map(),
devToolsConfig$jscomp$inline_1179 = {
findFiberByHostInstance: getInstanceFromNode,
bundleType: 0,
version: "19.0.0-canary-7ffb2ad4",
version: "19.0.0-canary-0d80c3d3",
rendererPackageName: "react-native-renderer",
rendererConfig: {
getInspectorDataForInstance: getInspectorDataForInstance,
@@ -11333,7 +11338,7 @@ var roots = new Map(),
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-7ffb2ad4"
reconcilerVersion: "19.0.0-canary-0d80c3d3"
});
exports.createPortal = function (children, containerTag) {
return createPortal$1(
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<24fee48abc3bddda74443b28d17bbc3e>>
* @generated SignedSource<<a64d4ff9138061f9c517e2327f49b803>>
*/
"use strict";
@@ -26682,20 +26682,31 @@ to return true:wantsResponderID| |
} // Check if something threw
if (exitStatus === RootErrored) {
var originallyAttemptedLanes = lanes;
var lanesThatJustErrored = lanes;
var errorRetryLanes = getLanesToRetrySynchronouslyOnError(
root,
originallyAttemptedLanes
lanesThatJustErrored
);
if (errorRetryLanes !== NoLanes) {
lanes = errorRetryLanes;
exitStatus = recoverFromConcurrentError(
root,
originallyAttemptedLanes,
lanesThatJustErrored,
errorRetryLanes
);
renderWasConcurrent = false;
renderWasConcurrent = false; // Need to check the exit status again.
if (exitStatus !== RootErrored) {
// The root did not error this time. Restart the exit algorithm
// from the beginning.
// TODO: Refactor the exit algorithm to be less confusing. Maybe
// more branches + recursion instead of a loop. I think the only
// thing that causes it to be a loop is the RootDidNotComplete
// check. If that's true, then we don't need a loop/recursion
// at all.
continue;
}
}
}
@@ -30677,7 +30688,7 @@ to return true:wantsResponderID| |
return root;
}
var ReactVersion = "19.0.0-canary-ae39878d";
var ReactVersion = "19.0.0-canary-f860c0a2";
/*
* The `'' + value` pattern (used in perf-sensitive code) throws for Symbol
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<9314995819fee277c04cd34d21bec2c4>>
* @generated SignedSource<<be6315c6d94122ce59e8cac641a06317>>
*/
"use strict";
@@ -9267,13 +9267,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
@@ -10791,7 +10796,7 @@ var roots = new Map(),
devToolsConfig$jscomp$inline_1168 = {
findFiberByHostInstance: getInstanceFromTag,
bundleType: 0,
version: "19.0.0-canary-da7bd93e",
version: "19.0.0-canary-fd8b4cb6",
rendererPackageName: "react-native-renderer",
rendererConfig: {
getInspectorDataForInstance: getInspectorDataForInstance,
@@ -10834,7 +10839,7 @@ var internals$jscomp$inline_1452 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-da7bd93e"
reconcilerVersion: "19.0.0-canary-fd8b4cb6"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1453 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<fd734202c65fd13f05652a451948b4df>>
* @generated SignedSource<<9a1a6e70657fd7e1a2634cc973de0331>>
*/
"use strict";
@@ -9798,13 +9798,18 @@ function performConcurrentWorkOnRoot(root, didTimeout) {
root,
renderWasConcurrent
);
0 !== errorRetryLanes &&
if (
0 !== errorRetryLanes &&
((lanes = errorRetryLanes),
(exitStatus = recoverFromConcurrentError(
root,
renderWasConcurrent,
errorRetryLanes
)));
)),
(renderWasConcurrent = !1),
2 !== exitStatus)
)
continue;
}
if (1 === exitStatus) {
prepareFreshStack(root, 0);
@@ -11497,7 +11502,7 @@ var roots = new Map(),
devToolsConfig$jscomp$inline_1248 = {
findFiberByHostInstance: getInstanceFromTag,
bundleType: 0,
version: "19.0.0-canary-2571bd6c",
version: "19.0.0-canary-e1908119",
rendererPackageName: "react-native-renderer",
rendererConfig: {
getInspectorDataForInstance: getInspectorDataForInstance,
@@ -11553,7 +11558,7 @@ var roots = new Map(),
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "19.0.0-canary-2571bd6c"
reconcilerVersion: "19.0.0-canary-e1908119"
});
exports.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED = {
computeComponentStackForErrorReporting: function (reactTag) {