[Flight] Dedupe suspense boundaries when it has already been found earlier (#27408)
If we track a postponed SuspenseBoundary parent that we have to replay through before it has postponed and it postpones itself later, we need to upgrade it to a postponed replay boundary instead of adding two.
Sebastian Markbåge committed
Sep 23, 2023 at 12:33 UTC
69728fde0ab2822ba468d9aaaa0c856de3f8547d
2 files changed
+64
-9
packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
+47
@@ -840,4 +840,51 @@ describe('ReactDOMFizzStaticBrowser', () => {
840
</div>,
841
]);
842
});
843
+
844
+ // @gate enablePostpone
845
+ it('can postpone a boundary after it has already been added', async () => {
846
+ let prerendering = true;
847
+ function Postpone() {
848
+ if (prerendering) {
849
+ React.unstable_postpone();
850
+ }
851
+ return 'Hello';
852
+ }
853
+
854
+ function App() {
855
+ return (
856
+ <div>
857
+ <Suspense fallback="Loading...">
858
+ <Suspense fallback="Loading...">
859
+ <Postpone />
860
+ </Suspense>
861
+ <Postpone />
862
+ <Postpone />
863
+ </Suspense>
864
+ </div>
865
+ );
866
+ }
867
+
868
+ const prerendered = await ReactDOMFizzStatic.prerender(<App />);
869
+ expect(prerendered.postponed).not.toBe(null);
870
+
871
+ prerendering = false;
872
+
873
+ console.log(JSON.stringify(prerendered.postponed, null, 2));
874
+
875
+ const resumed = await ReactDOMFizzServer.resume(
876
+ <App />,
877
+ JSON.parse(JSON.stringify(prerendered.postponed)),
878
+ );
879
+
880
+ await readIntoContainer(prerendered.prelude);
881
+
882
+ expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
883
+
884
+ await readIntoContainer(resumed);
885
+
886
+ expect(getVisibleChildren(container)).toEqual(
887
+ <div>{['Hello', 'Hello', 'Hello']}</div>,
888
+ );
889
+ });
890
});
packages/react-server/src/ReactFizzServer.js
+17
-9
@@ -2490,15 +2490,23 @@ function trackPostpone(
2490
addToReplayParent(boundaryNode, boundaryKeyPath[0], trackedPostpones);
2491
return;
2492
} else {
2493
- const boundaryNode: ReplaySuspenseBoundary = [
2494
- boundaryKeyPath[1],
2495
- boundaryKeyPath[2],
2496
- children,
2497
- null,
2498
- boundary.rootSegmentID,
2499
- ];
2500
- trackedPostpones.workingMap.set(boundaryKeyPath, boundaryNode);
2501
- addToReplayParent(boundaryNode, boundaryKeyPath[0], trackedPostpones);
2493
+ let boundaryNode: void | ReplayNode =
2494
+ trackedPostpones.workingMap.get(boundaryKeyPath);
2495
+ if (boundaryNode === undefined) {
2496
+ boundaryNode = [
2497
+ boundaryKeyPath[1],
2498
+ boundaryKeyPath[2],
2499
+ children,
2500
+ null,
2501
+ boundary.rootSegmentID,
2502
+ ];
2503
+ trackedPostpones.workingMap.set(boundaryKeyPath, boundaryNode);
2504
+ addToReplayParent(boundaryNode, boundaryKeyPath[0], trackedPostpones);
2505
+ } else {
2506
+ // Upgrade to ReplaySuspenseBoundary.
2507
+ ((boundaryNode: any): ReplaySuspenseBoundary)[4] =
2508
+ boundary.rootSegmentID;
2509
+ }
2510
// Fall through to add the child node.
2511
}
2512
}