[Fizz] Reset the segent id assignment when postponing the root (#33755)
When postponing the root we encode the segment Id into the postponed state but we should really be reseting it to zero so we can restart the counter from the beginning when the resume is actually just a re-render. This also no longer assigns the root segment id based on the postponed state when resuming the root for the same reason. In the future we may use the embedded replay segment id if we implement resuming the root without re-rendering everything but that is not yet implemented or planned.
Josh Story committed
Jul 10, 2025 at 12:12 UTC
463b808176ad7c9429a4981bb45a1da225fd4b85
2 files changed
+12
-9
packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
+3
-3
@@ -1623,7 +1623,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
1623
1624
expect(result).toBe(
1625
'<!DOCTYPE html><html><head><link rel="expect" href="#_R_" blocking="render"/></head>' +
1626
- '<body>hello<!--$?--><template id="B:1"></template><!--/$--><script id="_R_">requestAnimationFrame(function(){$RT=performance.now()});</script>',
1626
+ '<body>hello<!--$?--><template id="B:0"></template><!--/$--><script id="_R_">requestAnimationFrame(function(){$RT=performance.now()});</script>',
1627
);
1628
1629
await 1;
@@ -1648,8 +1648,8 @@ describe('ReactDOMFizzStaticBrowser', () => {
1648
1649
expect(slice).toBe(
1650
'<!DOCTYPE html><html><head><link rel="expect" href="#_R_" blocking="render"/></head>' +
1651
- '<body>hello<!--$?--><template id="B:1"></template><!--/$--><script id="_R_">requestAnimationFrame(function(){$RT=performance.now()});</script>' +
1652
- '<div hidden id="S:1">world<!-- --></div><script>$RX',
1651
+ '<body>hello<!--$?--><template id="B:0"></template><!--/$--><script id="_R_">requestAnimationFrame(function(){$RT=performance.now()});</script>' +
1652
+ '<div hidden id="S:0">world<!-- --></div><script>$RX',
1653
);
1654
});
1655
packages/react-server/src/ReactFizzServer.js
+9
-6
@@ -666,7 +666,6 @@ export function resumeRequest(
666
request.nextSegmentId = postponedState.nextSegmentId;
667
668
if (typeof postponedState.replaySlots === 'number') {
669
- const resumedId = postponedState.replaySlots;
669
// We have a resume slot at the very root. This is effectively just a full rerender.
670
const rootSegment = createPendingSegment(
671
request,
@@ -677,7 +676,6 @@ export function resumeRequest(
676
false,
677
false,
678
);
680
- rootSegment.id = resumedId;
679
// There is no parent so conceptually, we're unblocked to flush this segment.
680
rootSegment.parentFlushed = true;
681
const rootTask = createRenderTask(
@@ -6326,6 +6324,7 @@ export function getPostponedState(request: Request): null | PostponedState {
6324
return null;
6325
}
6326
let replaySlots: ResumeSlots;
6327
+ let nextSegmentId: number;
6328
if (
6329
request.completedRootSegment !== null &&
6330
// The Root postponed
@@ -6333,17 +6332,21 @@ export function getPostponedState(request: Request): null | PostponedState {
6332
// Or the Preamble was not available
6333
request.completedPreambleSegments === null)
6334
) {
6336
- // This is necessary for the pending preamble case and is idempotent for the
6337
- // postponed root case
6338
- replaySlots = request.completedRootSegment.id;
6335
+ nextSegmentId = 0;
6336
+ // We need to ensure that on resume we retry the root. We use a number
6337
+ // type for the replaySlots to signify this (see resumeRequest).
6338
+ // The value -1 represents an unassigned ID but is not functionally meaningful
6339
+ // for resuming at the root.
6340
+ replaySlots = -1;
6341
// We either postponed the root or we did not have a preamble to flush
6342
resetResumableState(request.resumableState, request.renderState);
6343
} else {
6344
+ nextSegmentId = request.nextSegmentId;
6345
replaySlots = trackedPostpones.rootSlots;
6346
completeResumableState(request.resumableState);
6347
}
6348
return {
6346
- nextSegmentId: request.nextSegmentId,
6349
+ nextSegmentId,
6350
rootFormatContext: request.rootFormatContext,
6351
progressiveChunkSize: request.progressiveChunkSize,
6352
resumableState: request.resumableState,