diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
index 6873031207fd5..e29fc3349e85e 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
@@ -493,6 +493,46 @@ describe('ReactDOMFizzStaticBrowser', () => {
// TODO: expect(getVisibleChildren(container)).toEqual(
Hello
);
});
+ it('supports postponing in lazy in prerender and resuming later', async () => {
+ let prerendering = true;
+ const Hole = React.lazy(async () => {
+ React.unstable_postpone();
+ });
+
+ function Postpone() {
+ return 'Hello';
+ }
+
+ function App() {
+ return (
+
+
+ Hi
+ {prerendering ? Hole : }
+
+
+ );
+ }
+
+ const prerendered = await ReactDOMFizzStatic.prerender();
+ expect(prerendered.postponed).not.toBe(null);
+
+ prerendering = false;
+
+ const resumed = await ReactDOMFizzServer.resume(
+ ,
+ prerendered.postponed,
+ );
+
+ await readIntoContainer(prerendered.prelude);
+
+ expect(getVisibleChildren(container)).toEqual(Loading...
);
+
+ await readIntoContainer(resumed);
+
+ // TODO: expect(getVisibleChildren(container)).toEqual(Hello
);
+ });
+
// @gate enablePostpone
it('only emits end tags once when resuming', async () => {
let prerendering = true;
diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js
index 318b3cac95af0..dd42e81ef667f 100644
--- a/packages/react-server/src/ReactFizzServer.js
+++ b/packages/react-server/src/ReactFizzServer.js
@@ -186,8 +186,7 @@ type ResumableNode =
| ResumableParentNode
| [
2, // RESUME_SEGMENT
- string | null /* name */,
- string | number /* key */,
+ number /* index */,
number /* segment id */,
];
@@ -220,6 +219,7 @@ type SuspenseBoundary = {
export type Task = {
node: ReactNodeList,
+ childIndex: number,
ping: () => void,
blockedBoundary: Root | SuspenseBoundary,
blockedSegment: Segment, // the segment we'll write to
@@ -1632,6 +1632,7 @@ function renderNodeDestructiveImpl(
// Stash the node we're working on. We'll pick up from this task in case
// something suspends.
task.node = node;
+ task.childIndex = childIndex;
// Handle object types
if (typeof node === 'object' && node !== null) {
@@ -1831,6 +1832,7 @@ function trackPostpone(
request: Request,
trackedPostpones: PostponedHoles,
task: Task,
+ childIndex: number,
segment: Segment,
): void {
segment.status = POSTPONED;
@@ -1862,7 +1864,7 @@ function trackPostpone(
boundary.id,
];
trackedPostpones.workingMap.set(boundaryKeyPath, boundaryNode);
- addToResumableParent(boundaryNode, boundaryKeyPath, trackedPostpones);
+ addToResumableParent(boundaryNode, boundaryKeyPath[0], trackedPostpones);
}
const keyPath = task.keyPath;
@@ -1872,12 +1874,7 @@ function trackPostpone(
);
}
- const segmentNode: ResumableNode = [
- RESUME_SEGMENT,
- keyPath[1],
- keyPath[2],
- segment.id,
- ];
+ const segmentNode: ResumableNode = [RESUME_SEGMENT, childIndex, segment.id];
addToResumableParent(segmentNode, keyPath, trackedPostpones);
}
@@ -1941,6 +1938,7 @@ function spawnNewSuspendedTask(
task.context,
task.treeContext,
);
+ newTask.childIndex = task.childIndex;
if (__DEV__) {
if (task.componentStack !== null) {
@@ -2035,7 +2033,13 @@ function renderNode(
task,
postponeInstance.message,
);
- trackPostpone(request, trackedPostpones, task, postponedSegment);
+ trackPostpone(
+ request,
+ trackedPostpones,
+ task,
+ childIndex,
+ postponedSegment,
+ );
// Restore the context. We assume that this will be restored by the inner
// functions in case nothing throws so we don't use "finally" here.
@@ -2328,7 +2332,13 @@ function retryTask(request: Request, task: Task): void {
const prevThenableState = task.thenableState;
task.thenableState = null;
- renderNodeDestructive(request, task, prevThenableState, task.node, 0);
+ renderNodeDestructive(
+ request,
+ task,
+ prevThenableState,
+ task.node,
+ task.childIndex,
+ );
pushSegmentFinale(
segment.chunks,
request.renderState,
@@ -2377,8 +2387,15 @@ function retryTask(request: Request, task: Task): void {
task.abortSet.delete(task);
const postponeInstance: Postpone = (x: any);
logPostpone(request, postponeInstance.message);
- trackPostpone(request, trackedPostpones, task, segment);
+ trackPostpone(
+ request,
+ trackedPostpones,
+ task,
+ task.childIndex,
+ segment,
+ );
finishedTask(request, task.blockedBoundary, segment);
+ return;
}
}
task.abortSet.delete(task);
@@ -2975,10 +2992,9 @@ export function getResumableState(request: Request): ResumableState {
function addToResumableParent(
node: ResumableNode,
- keyPath: KeyNode,
+ parentKeyPath: Root | KeyNode,
trackedPostpones: PostponedHoles,
): void {
- const parentKeyPath = keyPath[0];
if (parentKeyPath === null) {
trackedPostpones.root.push(node);
} else {
@@ -2992,7 +3008,7 @@ function addToResumableParent(
([]: Array),
]: ResumableParentNode);
workingMap.set(parentKeyPath, parentNode);
- addToResumableParent(parentNode, parentKeyPath, trackedPostpones);
+ addToResumableParent(parentNode, parentKeyPath[0], trackedPostpones);
}
parentNode[3].push(node);
}