Skip to content

Commit

Permalink
Client render dehydrated Suspense boundaries on document load (facebo…
Browse files Browse the repository at this point in the history
…ok#31620)

When streaming SSR while hydrating React will wait for Suspense
boundaries to be revealed by the SSR stream before attempting to hydrate
them. The rationale here is that the Server render is likely further
ahead of whatever the client would produce so waiting to let the server
stream in the UI is preferable to retrying on the client and possibly
delaying how quickly the primary content becomes available. However If
the connection closes early (user hits stop for instance) or there is a
server error which prevents additional HTML from being delivered to the
client this can put React into a broken state where the boundary never
resolves nor errors and the hydration never retries that boundary
freezing it in it's fallback state.

Once the document has fully loaded we know there is not way any
additional Suspense boundaries can arrive. This update changes react-dom
on the client to schedule client renders for any unfinished Suspense
boundaries upon document loading.

The technique for client rendering a fallback is pretty straight
forward. When hydrating a Suspense boundary if the Document is in
'complete' readyState we interpret pending boundaries as fallback
boundaries. If the readyState is not 'complete' we register an event to
retry the boundary when the DOMContentLoaded event fires.

To test this I needed JSDOM to model readyState. We previously had a
temporary implementation of readyState for SSR streaming but I ended up
implementing this as a mock of JSDOM that implements a fake readyState
that is mutable. It starts off in 'loading' readyState and you can
advance it by mutating document.readyState. You can also reset it to
'loading'. It fires events when changing states.

This seems like the least invasive way to get closer-to-real-browser
behavior in a way that won't require remembering this subtle detail
every time you create a test that asserts Suspense resolution order.

DiffTrain build for [16d2bbb](facebook@16d2bbb)
  • Loading branch information
pull[bot] committed Dec 4, 2024
1 parent 9ece68a commit 879551c
Show file tree
Hide file tree
Showing 34 changed files with 1,160 additions and 960 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2a9f4c04e54294b668e0a2ae11c1930c2e57b248
16d2bbbd1f1617d636ea0fd271b902a12a763c27
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION_TRANSFORMS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2a9f4c04e54294b668e0a2ae11c1930c2e57b248
16d2bbbd1f1617d636ea0fd271b902a12a763c27
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -1841,7 +1841,7 @@ __DEV__ &&
exports.useTransition = function () {
return resolveDispatcher().useTransition();
};
exports.version = "19.0.0-www-classic-2a9f4c04-20241122";
exports.version = "19.0.0-www-classic-16d2bbbd-20241203";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -1841,7 +1841,7 @@ __DEV__ &&
exports.useTransition = function () {
return resolveDispatcher().useTransition();
};
exports.version = "19.0.0-www-modern-2a9f4c04-20241122";
exports.version = "19.0.0-www-modern-16d2bbbd-20241203";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -634,4 +634,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactSharedInternals.H.useTransition();
};
exports.version = "19.0.0-www-classic-2a9f4c04-20241122";
exports.version = "19.0.0-www-classic-16d2bbbd-20241203";
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -634,4 +634,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactSharedInternals.H.useTransition();
};
exports.version = "19.0.0-www-modern-2a9f4c04-20241122";
exports.version = "19.0.0-www-modern-16d2bbbd-20241203";
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-profiling.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactSharedInternals.H.useTransition();
};
exports.version = "19.0.0-www-classic-2a9f4c04-20241122";
exports.version = "19.0.0-www-classic-16d2bbbd-20241203";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-profiling.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,7 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactSharedInternals.H.useTransition();
};
exports.version = "19.0.0-www-modern-2a9f4c04-20241122";
exports.version = "19.0.0-www-modern-16d2bbbd-20241203";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactART-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -17125,11 +17125,11 @@ __DEV__ &&
(function () {
var internals = {
bundleType: 1,
version: "19.0.0-www-classic-2a9f4c04-20241122",
version: "19.0.0-www-classic-16d2bbbd-20241203",
rendererPackageName: "react-art",
currentDispatcherRef: ReactSharedInternals,
findFiberByHostInstance: getInstanceFromNode,
reconcilerVersion: "19.0.0-www-classic-2a9f4c04-20241122"
reconcilerVersion: "19.0.0-www-classic-16d2bbbd-20241203"
};
internals.overrideHookState = overrideHookState;
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
Expand Down Expand Up @@ -17163,7 +17163,7 @@ __DEV__ &&
exports.Shape = Shape;
exports.Surface = Surface;
exports.Text = Text;
exports.version = "19.0.0-www-classic-2a9f4c04-20241122";
exports.version = "19.0.0-www-classic-16d2bbbd-20241203";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactART-dev.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -16888,11 +16888,11 @@ __DEV__ &&
(function () {
var internals = {
bundleType: 1,
version: "19.0.0-www-modern-2a9f4c04-20241122",
version: "19.0.0-www-modern-16d2bbbd-20241203",
rendererPackageName: "react-art",
currentDispatcherRef: ReactSharedInternals,
findFiberByHostInstance: getInstanceFromNode,
reconcilerVersion: "19.0.0-www-modern-2a9f4c04-20241122"
reconcilerVersion: "19.0.0-www-modern-16d2bbbd-20241203"
};
internals.overrideHookState = overrideHookState;
internals.overrideHookStateDeletePath = overrideHookStateDeletePath;
Expand Down Expand Up @@ -16926,7 +16926,7 @@ __DEV__ &&
exports.Shape = Shape;
exports.Surface = Surface;
exports.Text = Text;
exports.version = "19.0.0-www-modern-2a9f4c04-20241122";
exports.version = "19.0.0-www-modern-16d2bbbd-20241203";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
Expand Down
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactART-prod.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -10824,13 +10824,13 @@ var slice = Array.prototype.slice,
})(React.Component);
var internals$jscomp$inline_1509 = {
bundleType: 0,
version: "19.0.0-www-classic-2a9f4c04-20241122",
version: "19.0.0-www-classic-16d2bbbd-20241203",
rendererPackageName: "react-art",
currentDispatcherRef: ReactSharedInternals,
findFiberByHostInstance: function () {
return null;
},
reconcilerVersion: "19.0.0-www-classic-2a9f4c04-20241122"
reconcilerVersion: "19.0.0-www-classic-16d2bbbd-20241203"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1510 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand All @@ -10856,4 +10856,4 @@ exports.RadialGradient = RadialGradient;
exports.Shape = TYPES.SHAPE;
exports.Surface = Surface;
exports.Text = Text;
exports.version = "19.0.0-www-classic-2a9f4c04-20241122";
exports.version = "19.0.0-www-classic-16d2bbbd-20241203";
6 changes: 3 additions & 3 deletions compiled/facebook-www/ReactART-prod.modern.js
Original file line number Diff line number Diff line change
Expand Up @@ -10540,13 +10540,13 @@ var slice = Array.prototype.slice,
})(React.Component);
var internals$jscomp$inline_1488 = {
bundleType: 0,
version: "19.0.0-www-modern-2a9f4c04-20241122",
version: "19.0.0-www-modern-16d2bbbd-20241203",
rendererPackageName: "react-art",
currentDispatcherRef: ReactSharedInternals,
findFiberByHostInstance: function () {
return null;
},
reconcilerVersion: "19.0.0-www-modern-2a9f4c04-20241122"
reconcilerVersion: "19.0.0-www-modern-16d2bbbd-20241203"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1489 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
Expand All @@ -10572,4 +10572,4 @@ exports.RadialGradient = RadialGradient;
exports.Shape = TYPES.SHAPE;
exports.Surface = Surface;
exports.Text = Text;
exports.version = "19.0.0-www-modern-2a9f4c04-20241122";
exports.version = "19.0.0-www-modern-16d2bbbd-20241203";
Loading

0 comments on commit 879551c

Please sign in to comment.