Skip to content

Commit 0ed6ceb

Browse files
authored
[Fizz] Add "Queued" Status to SSR:ed Suspense Boundaries (facebook#33087)
Stacked on facebook#33076. This fixes a bug where we used the "complete" status but the DOMContentLoaded event. This checks for not "loading" instead. We also add a new status where the boundary has been marked as complete by the server but has not yet flushed either due to being throttled, suspended on CSS or animating.
1 parent ee7fee8 commit 0ed6ceb

File tree

3 files changed

+42
-15
lines changed

3 files changed

+42
-15
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

+26-13
Original file line numberDiff line numberDiff line change
@@ -238,14 +238,15 @@ const ACTIVITY_END_DATA = '/&';
238238
const SUSPENSE_START_DATA = '$';
239239
const SUSPENSE_END_DATA = '/$';
240240
const SUSPENSE_PENDING_START_DATA = '$?';
241+
const SUSPENSE_QUEUED_START_DATA = '$~';
241242
const SUSPENSE_FALLBACK_START_DATA = '$!';
242243
const PREAMBLE_CONTRIBUTION_HTML = 'html';
243244
const PREAMBLE_CONTRIBUTION_BODY = 'body';
244245
const PREAMBLE_CONTRIBUTION_HEAD = 'head';
245246
const FORM_STATE_IS_MATCHING = 'F!';
246247
const FORM_STATE_IS_NOT_MATCHING = 'F';
247248

248-
const DOCUMENT_READY_STATE_COMPLETE = 'complete';
249+
const DOCUMENT_READY_STATE_LOADING = 'loading';
249250

250251
const STYLE = 'style';
251252

@@ -1084,6 +1085,7 @@ function clearHydrationBoundary(
10841085
} else if (
10851086
data === SUSPENSE_START_DATA ||
10861087
data === SUSPENSE_PENDING_START_DATA ||
1088+
data === SUSPENSE_QUEUED_START_DATA ||
10871089
data === SUSPENSE_FALLBACK_START_DATA ||
10881090
data === ACTIVITY_START_DATA
10891091
) {
@@ -1205,6 +1207,7 @@ function hideOrUnhideDehydratedBoundary(
12051207
} else if (
12061208
data === SUSPENSE_START_DATA ||
12071209
data === SUSPENSE_PENDING_START_DATA ||
1210+
data === SUSPENSE_QUEUED_START_DATA ||
12081211
data === SUSPENSE_FALLBACK_START_DATA
12091212
) {
12101213
depth++;
@@ -3140,7 +3143,10 @@ export function canHydrateSuspenseInstance(
31403143
}
31413144
31423145
export function isSuspenseInstancePending(instance: SuspenseInstance): boolean {
3143-
return instance.data === SUSPENSE_PENDING_START_DATA;
3146+
return (
3147+
instance.data === SUSPENSE_PENDING_START_DATA ||
3148+
instance.data === SUSPENSE_QUEUED_START_DATA
3149+
);
31443150
}
31453151
31463152
export function isSuspenseInstanceFallback(
@@ -3149,7 +3155,7 @@ export function isSuspenseInstanceFallback(
31493155
return (
31503156
instance.data === SUSPENSE_FALLBACK_START_DATA ||
31513157
(instance.data === SUSPENSE_PENDING_START_DATA &&
3152-
instance.ownerDocument.readyState === DOCUMENT_READY_STATE_COMPLETE)
3158+
instance.ownerDocument.readyState !== DOCUMENT_READY_STATE_LOADING)
31533159
);
31543160
}
31553161
@@ -3192,15 +3198,19 @@ export function registerSuspenseInstanceRetry(
31923198
callback: () => void,
31933199
) {
31943200
const ownerDocument = instance.ownerDocument;
3195-
if (
3201+
if (instance.data === SUSPENSE_QUEUED_START_DATA) {
3202+
// The Fizz runtime has already queued this boundary for reveal. We wait for it
3203+
// to be revealed and then retries.
3204+
instance._reactRetry = callback;
3205+
} else if (
31963206
// The Fizz runtime must have put this boundary into client render or complete
31973207
// state after the render finished but before it committed. We need to call the
31983208
// callback now rather than wait
31993209
instance.data !== SUSPENSE_PENDING_START_DATA ||
32003210
// The boundary is still in pending status but the document has finished loading
32013211
// before we could register the event handler that would have scheduled the retry
32023212
// on load so we call teh callback now.
3203-
ownerDocument.readyState === DOCUMENT_READY_STATE_COMPLETE
3213+
ownerDocument.readyState !== DOCUMENT_READY_STATE_LOADING
32043214
) {
32053215
callback();
32063216
} else {
@@ -3255,18 +3265,19 @@ function getNextHydratable(node: ?Node) {
32553265
break;
32563266
}
32573267
if (nodeType === COMMENT_NODE) {
3258-
const nodeData = (node: any).data;
3268+
const data = (node: any).data;
32593269
if (
3260-
nodeData === SUSPENSE_START_DATA ||
3261-
nodeData === SUSPENSE_FALLBACK_START_DATA ||
3262-
nodeData === SUSPENSE_PENDING_START_DATA ||
3263-
nodeData === ACTIVITY_START_DATA ||
3264-
nodeData === FORM_STATE_IS_MATCHING ||
3265-
nodeData === FORM_STATE_IS_NOT_MATCHING
3270+
data === SUSPENSE_START_DATA ||
3271+
data === SUSPENSE_FALLBACK_START_DATA ||
3272+
data === SUSPENSE_PENDING_START_DATA ||
3273+
data === SUSPENSE_QUEUED_START_DATA ||
3274+
data === ACTIVITY_START_DATA ||
3275+
data === FORM_STATE_IS_MATCHING ||
3276+
data === FORM_STATE_IS_NOT_MATCHING
32663277
) {
32673278
break;
32683279
}
3269-
if (nodeData === SUSPENSE_END_DATA || nodeData === ACTIVITY_END_DATA) {
3280+
if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {
32703281
return null;
32713282
}
32723283
}
@@ -3494,6 +3505,7 @@ function getNextHydratableInstanceAfterHydrationBoundary(
34943505
data === SUSPENSE_START_DATA ||
34953506
data === SUSPENSE_FALLBACK_START_DATA ||
34963507
data === SUSPENSE_PENDING_START_DATA ||
3508+
data === SUSPENSE_QUEUED_START_DATA ||
34973509
data === ACTIVITY_START_DATA
34983510
) {
34993511
depth++;
@@ -3535,6 +3547,7 @@ export function getParentHydrationBoundary(
35353547
data === SUSPENSE_START_DATA ||
35363548
data === SUSPENSE_FALLBACK_START_DATA ||
35373549
data === SUSPENSE_PENDING_START_DATA ||
3550+
data === SUSPENSE_QUEUED_START_DATA ||
35383551
data === ACTIVITY_START_DATA
35393552
) {
35403553
if (depth === 0) {

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js

+14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const ACTIVITY_END_DATA = '/&';
99
const SUSPENSE_START_DATA = '$';
1010
const SUSPENSE_END_DATA = '/$';
1111
const SUSPENSE_PENDING_START_DATA = '$?';
12+
const SUSPENSE_QUEUED_START_DATA = '$~';
1213
const SUSPENSE_FALLBACK_START_DATA = '$!';
1314

1415
// TODO: Symbols that are referenced outside this module use dynamic accessor
@@ -106,6 +107,7 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
106107
} else if (
107108
data === SUSPENSE_START_DATA ||
108109
data === SUSPENSE_PENDING_START_DATA ||
110+
data === SUSPENSE_QUEUED_START_DATA ||
109111
data === SUSPENSE_FALLBACK_START_DATA ||
110112
data === ACTIVITY_START_DATA
111113
) {
@@ -132,6 +134,10 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
132134
}
133135
}
134136

137+
// Mark this Suspense boundary as queued so we know not to client render it
138+
// at the end of document load.
139+
const suspenseNodeOuter = suspenseIdNodeOuter.previousSibling;
140+
suspenseNodeOuter.data = SUSPENSE_QUEUED_START_DATA;
135141
// Queue this boundary for the next batch
136142
window['$RB'].push(suspenseIdNodeOuter, contentNodeOuter);
137143

@@ -261,6 +267,14 @@ export function completeBoundaryWithStyles(
261267
}
262268
}
263269

270+
const suspenseIdNodeOuter = document.getElementById(suspenseBoundaryID);
271+
if (suspenseIdNodeOuter) {
272+
// Mark this Suspense boundary as queued so we know not to client render it
273+
// at the end of document load.
274+
const suspenseNodeOuter = suspenseIdNodeOuter.previousSibling;
275+
suspenseNodeOuter.data = SUSPENSE_QUEUED_START_DATA;
276+
}
277+
264278
Promise.all(dependencies).then(
265279
window['$RC'].bind(null, suspenseBoundaryID, contentID),
266280
window['$RX'].bind(null, suspenseBoundaryID, 'CSS failed to load'),

0 commit comments

Comments
 (0)