Skip to content

Commit 1c50823

Browse files
committed
Apply optimization in render phase before flushing
1 parent faf21ef commit 1c50823

File tree

2 files changed

+74
-27
lines changed

2 files changed

+74
-27
lines changed

packages/react-dom/src/__tests__/ReactDOMFizzSuspenseList-test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,39 @@ describe('ReactDOMFizSuspenseList', () => {
254254
);
255255
});
256256

257+
// @gate enableSuspenseList
258+
it('displays all "together" in a single pass', async () => {
259+
function Foo() {
260+
return (
261+
<div>
262+
<SuspenseList revealOrder="together">
263+
<Suspense fallback={<Text text="Loading A" />}>
264+
<Text text="A" />
265+
</Suspense>
266+
<Suspense fallback={<Text text="Loading B" />}>
267+
<Text text="B" />
268+
</Suspense>
269+
<Suspense fallback={<Text text="Loading C" />}>
270+
<Text text="C" />
271+
</Suspense>
272+
</SuspenseList>
273+
</div>
274+
);
275+
}
276+
277+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<Foo />);
278+
pipe(writable);
279+
await 0;
280+
const bufferedContent = buffer;
281+
buffer = '';
282+
283+
assertLog(['A', 'B', 'C', 'Loading A', 'Loading B', 'Loading C']);
284+
285+
expect(bufferedContent).toMatchInlineSnapshot(
286+
`"<div><!--$--><span>A</span><!--/$--><!--$--><span>B</span><!--/$--><!--$--><span>C</span><!--/$--></div>"`,
287+
);
288+
});
289+
257290
// @gate enableSuspenseList
258291
it('displays all "together" even when nested as siblings', async () => {
259292
const A = createAsyncText('A');

packages/react-server/src/ReactFizzServer.js

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,6 +1405,11 @@ function renderSuspenseBoundary(
14051405
}
14061406
return;
14071407
}
1408+
} else {
1409+
const boundaryRow = prevRow;
1410+
if (boundaryRow !== null && boundaryRow.together) {
1411+
tryToResolveTogetherRow(request, boundaryRow);
1412+
}
14081413
}
14091414
} catch (thrownValue: mixed) {
14101415
newBoundary.status = CLIENT_RENDERED;
@@ -1699,6 +1704,34 @@ function unblockSuspenseListRow(
16991704
}
17001705
}
17011706

1707+
function tryToResolveTogetherRow(
1708+
request: Request,
1709+
togetherRow: SuspenseListRow,
1710+
): void {
1711+
// If we have a "together" row and all the pendingTasks are really the boundaries themselves,
1712+
// and we won't outline any of them then we can unblock this row early so that we can inline
1713+
// all the boundaries at once.
1714+
const boundaries = togetherRow.boundaries;
1715+
if (boundaries === null || togetherRow.pendingTasks !== boundaries.length) {
1716+
return;
1717+
}
1718+
let allCompleteAndInlinable = true;
1719+
for (let i = 0; i < boundaries.length; i++) {
1720+
const rowBoundary = boundaries[i];
1721+
if (
1722+
rowBoundary.pendingTasks !== 1 ||
1723+
rowBoundary.parentFlushed ||
1724+
isEligibleForOutlining(request, rowBoundary)
1725+
) {
1726+
allCompleteAndInlinable = false;
1727+
break;
1728+
}
1729+
}
1730+
if (allCompleteAndInlinable) {
1731+
unblockSuspenseListRow(request, togetherRow);
1732+
}
1733+
}
1734+
17021735
function createSuspenseListRow(
17031736
previousRow: null | SuspenseListRow,
17041737
): SuspenseListRow {
@@ -4787,6 +4820,8 @@ function finishedTask(
47874820
if (row !== null) {
47884821
if (--row.pendingTasks === 0) {
47894822
finishSuspenseListRow(request, row);
4823+
} else if (row.together) {
4824+
tryToResolveTogetherRow(request, row);
47904825
}
47914826
}
47924827
request.allPendingTasks--;
@@ -4876,6 +4911,10 @@ function finishedTask(
48764911
}
48774912
}
48784913
}
4914+
const boundaryRow = boundary.row;
4915+
if (boundaryRow !== null && boundaryRow.together) {
4916+
tryToResolveTogetherRow(request, boundaryRow);
4917+
}
48794918
}
48804919
}
48814920

@@ -5394,40 +5433,14 @@ function flushSegment(
53945433
return flushSubtree(request, destination, segment, hoistableState);
53955434
}
53965435

5397-
const row = boundary.row;
5398-
if (
5399-
boundary.status === PENDING &&
5400-
row !== null &&
5401-
row.together &&
5402-
row.boundaries !== null &&
5403-
row.pendingTasks === row.boundaries.length
5404-
) {
5405-
// If we have a "together" row and all the pendingTasks are really the boundaries themselves,
5406-
// and we won't outline any of them then we can unblock this row early so that we can inline
5407-
// all the boundaries at once.
5408-
let allComplete = true;
5409-
for (let i = 0; i < row.boundaries.length; i++) {
5410-
const rowBoundary = row.boundaries[i];
5411-
if (
5412-
rowBoundary.pendingTasks !== 1 ||
5413-
isEligibleForOutlining(request, rowBoundary)
5414-
) {
5415-
allComplete = false;
5416-
break;
5417-
}
5418-
}
5419-
if (allComplete) {
5420-
unblockSuspenseListRow(request, row);
5421-
}
5422-
}
5423-
54245436
boundary.parentFlushed = true;
54255437
// This segment is a Suspense boundary. We need to decide whether to
54265438
// emit the content or the fallback now.
54275439
if (boundary.status === CLIENT_RENDERED) {
54285440
// Emit a client rendered suspense boundary wrapper.
54295441
// We never queue the inner boundary so we'll never emit its content or partial segments.
54305442

5443+
const row = boundary.row;
54315444
if (row !== null) {
54325445
// Since this boundary end up client rendered, we can unblock future suspense list rows.
54335446
// This means that they may appear out of order if the future rows succeed but this is
@@ -5526,6 +5539,7 @@ function flushSegment(
55265539
hoistHoistables(hoistableState, boundary.contentState);
55275540
}
55285541

5542+
const row = boundary.row;
55295543
if (row !== null && isEligibleForOutlining(request, boundary)) {
55305544
// Once we have written the boundary, we can unblock the row and let future
55315545
// rows be written. This may schedule new completed boundaries.

0 commit comments

Comments
 (0)