Repository: facebook/react
Pull request: [Fizz] Reset the segent id assignment when postponing the root

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.

Changed files:
filename: packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
patch: @@ -1623,7 +1623,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
 
     expect(result).toBe(
       '<!DOCTYPE html><html><head><link rel="expect" href="#_R_" blocking="render"/></head>' +
-        '<body>hello<!--$?--><template id="B:1"></template><!--/$--><script id="_R_">requestAnimationFrame(function(){$RT=performance.now()});</script>',
+        '<body>hello<!--$?--><template id="B:0"></template><!--/$--><script id="_R_">requestAnimationFrame(function(){$RT=performance.now()});</script>',
     );
 
     await 1;
@@ -1648,8 +1648,8 @@ describe('ReactDOMFizzStaticBrowser', () => {
 
     expect(slice).toBe(
       '<!DOCTYPE html><html><head><link rel="expect" href="#_R_" blocking="render"/></head>' +
-        '<body>hello<!--$?--><template id="B:1"></template><!--/$--><script id="_R_">requestAnimationFrame(function(){$RT=performance.now()});</script>' +
-        '<div hidden id="S:1">world<!-- --></div><script>$RX',
+        '<body>hello<!--$?--><template id="B:0"></template><!--/$--><script id="_R_">requestAnimationFrame(function(){$RT=performance.now()});</script>' +
+        '<div hidden id="S:0">world<!-- --></div><script>$RX',
     );
   });
 

filename: packages/react-server/src/ReactFizzServer.js
patch: @@ -666,7 +666,6 @@ export function resumeRequest(
   request.nextSegmentId = postponedState.nextSegmentId;
 
   if (typeof postponedState.replaySlots === 'number') {
-    const resumedId = postponedState.replaySlots;
     // We have a resume slot at the very root. This is effectively just a full rerender.
     const rootSegment = createPendingSegment(
       request,
@@ -677,7 +676,6 @@ export function resumeRequest(
       false,
       false,
     );
-    rootSegment.id = resumedId;
     // There is no parent so conceptually, we're unblocked to flush this segment.
     rootSegment.parentFlushed = true;
     const rootTask = createRenderTask(
@@ -6326,24 +6324,29 @@ export function getPostponedState(request: Request): null | PostponedState {
     return null;
   }
   let replaySlots: ResumeSlots;
+  let nextSegmentId: number;
   if (
     request.completedRootSegment !== null &&
     // The Root postponed
     (request.completedRootSegment.status === POSTPONED ||
       // Or the Preamble was not available
       request.completedPreambleSegments === null)
   ) {
-    // This is necessary for the pending preamble case and is idempotent for the
-    // postponed root case
-    replaySlots = request.completedRootSegment.id;
+    nextSegmentId = 0;
+    // We need to ensure that on resume we retry the root. We use a number
+    // type for the replaySlots to signify this (see resumeRequest).
+    // The value -1 represents an unassigned ID but is not functionally meaningful
+    // for resuming at the root.
+    replaySlots = -1;
     // We either postponed the root or we did not have a preamble to flush
     resetResumableState(request.resumableState, request.renderState);
   } else {
+    nextSegmentId = request.nextSegmentId;
     replaySlots = trackedPostpones.rootSlots;
     completeResumableState(request.resumableState);
   }
   return {
-    nextSegmentId: request.nextSegmentId,
+    nextSegmentId,
     rootFormatContext: request.rootFormatContext,
     progressiveChunkSize: request.progressiveChunkSize,
     resumableState: request.resumableState,