Skip to content

Commit 259832b

Browse files
committed
Clean up row that was blocked by an aborted boundary
1 parent 50389e1 commit 259832b

File tree

2 files changed

+69
-1
lines changed

2 files changed

+69
-1
lines changed

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

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ let writable;
2727
let container;
2828
let buffer = '';
2929
let hasErrored = false;
30+
let hasCompleted = false;
3031
let fatalError = undefined;
3132

32-
describe('ReactDOMFizSuspenseList', () => {
33+
describe('ReactDOMFizzSuspenseList', () => {
3334
beforeEach(() => {
3435
jest.resetModules();
3536
JSDOM = require('jsdom').JSDOM;
@@ -59,6 +60,7 @@ describe('ReactDOMFizSuspenseList', () => {
5960

6061
buffer = '';
6162
hasErrored = false;
63+
hasCompleted = false;
6264

6365
writable = new Stream.PassThrough();
6466
writable.setEncoding('utf8');
@@ -69,6 +71,9 @@ describe('ReactDOMFizSuspenseList', () => {
6971
hasErrored = true;
7072
fatalError = error;
7173
});
74+
writable.on('finish', () => {
75+
hasCompleted = true;
76+
});
7277
});
7378

7479
afterEach(() => {
@@ -714,4 +719,59 @@ describe('ReactDOMFizSuspenseList', () => {
714719
</div>,
715720
);
716721
});
722+
723+
// @gate enableSuspenseList
724+
it('can abort a pending SuspenseList', async () => {
725+
const A = createAsyncText('A');
726+
727+
function Foo() {
728+
return (
729+
<div>
730+
<SuspenseList revealOrder="forwards">
731+
<Suspense fallback={<Text text="Loading A" />}>
732+
<A />
733+
</Suspense>
734+
<Suspense fallback={<Text text="Loading B" />}>
735+
<Text text="B" />
736+
</Suspense>
737+
</SuspenseList>
738+
</div>
739+
);
740+
}
741+
742+
const errors = [];
743+
let abortStream;
744+
await serverAct(async () => {
745+
const {pipe, abort} = ReactDOMFizzServer.renderToPipeableStream(<Foo />, {
746+
onError(error) {
747+
errors.push(error.message);
748+
},
749+
});
750+
pipe(writable);
751+
abortStream = abort;
752+
});
753+
754+
assertLog([
755+
'Suspend! [A]',
756+
'B', // TODO: Defer rendering the content after fallback if previous suspended,
757+
'Loading A',
758+
'Loading B',
759+
]);
760+
761+
expect(getVisibleChildren(container)).toEqual(
762+
<div>
763+
<span>Loading A</span>
764+
<span>Loading B</span>
765+
</div>,
766+
);
767+
768+
await serverAct(() => {
769+
abortStream();
770+
});
771+
772+
expect(hasCompleted).toBe(true);
773+
expect(errors).toEqual([
774+
'The render was aborted by the server without a reason.',
775+
]);
776+
});
717777
});

packages/react-server/src/ReactFizzServer.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4706,6 +4706,14 @@ function abortTask(task: Task, request: Request, error: mixed): void {
47064706
}
47074707
}
47084708

4709+
const boundaryRow = boundary.row;
4710+
if (boundaryRow !== null) {
4711+
// Unblock the SuspenseListRow that was blocked by this boundary.
4712+
if (--boundaryRow.pendingTasks === 0) {
4713+
finishSuspenseListRow(request, boundaryRow);
4714+
}
4715+
}
4716+
47094717
// If this boundary was still pending then we haven't already cancelled its fallbacks.
47104718
// We'll need to abort the fallbacks, which will also error that parent boundary.
47114719
boundary.fallbackAbortableTasks.forEach(fallbackTask =>

0 commit comments

Comments
 (0)