diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 57124ec6e0c0e..2b9c77c08ba94 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -88,6 +88,7 @@ describe('ReactDOMFizzServer', () => { setTimeout(cb); container = document.getElementById('container'); + CSPnonce = null; Scheduler = require('scheduler'); React = require('react'); ReactDOM = require('react-dom'); @@ -10447,4 +10448,110 @@ describe('ReactDOMFizzServer', () => { , ); }); + + it('should not error when discarding deeply nested Suspense boundaries in a parent fallback partially complete before the parent boundary resolves', async () => { + let resolve1; + const promise1 = new Promise(r => (resolve1 = r)); + let resolve2; + const promise2 = new Promise(r => (resolve2 = r)); + const promise3 = new Promise(r => {}); + + function Use({children, promise}) { + React.use(promise); + return children; + } + function App() { + return ( +
+ + +
+ +
+ +
+ +
deep fallback
+
+
+
+
+
+
+
+
+ }> + Success! + + + ); + } + + await act(() => { + const {pipe} = renderToPipeableStream(); + pipe(writable); + }); + + expect(getVisibleChildren(container)).toEqual( +
+
Loading...
+
, + ); + + await act(() => { + resolve1('resolved'); + resolve2('resolved'); + }); + + expect(getVisibleChildren(container)).toEqual(
Success!
); + }); + + it('should not error when discarding deeply nested Suspense boundaries in a parent fallback partially complete before the parent boundary resolves with empty segments', async () => { + let resolve1; + const promise1 = new Promise(r => (resolve1 = r)); + let resolve2; + const promise2 = new Promise(r => (resolve2 = r)); + const promise3 = new Promise(r => {}); + + function Use({children, promise}) { + React.use(promise); + return children; + } + function App() { + return ( +
+ + + + +
deep fallback
+
+
+
+
+ }> + Success! + +
+ ); + } + + await act(() => { + const {pipe} = renderToPipeableStream(); + pipe(writable); + }); + + expect(getVisibleChildren(container)).toEqual(
Loading...
); + + await act(() => { + resolve1('resolved'); + resolve2('resolved'); + }); + + expect(getVisibleChildren(container)).toEqual(
Success!
); + }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js index b8a4e5b86ae91..a28d492b1cf83 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js @@ -25,7 +25,7 @@ let SuspenseList; let textCache; let loadCache; let writable; -const CSPnonce = null; +let CSPnonce = null; let container; let buffer = ''; let hasErrored = false; @@ -69,6 +69,7 @@ describe('ReactDOMFloat', () => { setTimeout(cb); container = document.getElementById('container'); + CSPnonce = null; React = require('react'); ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 19860614ad450..bff81ce607989 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -4918,7 +4918,11 @@ function queueCompletedSegment( const childSegment = segment.children[0]; childSegment.id = segment.id; childSegment.parentFlushed = true; - if (childSegment.status === COMPLETED) { + if ( + childSegment.status === COMPLETED || + childSegment.status === ABORTED || + childSegment.status === ERRORED + ) { queueCompletedSegment(boundary, childSegment); } } else { @@ -4989,7 +4993,7 @@ function finishedTask( // Our parent segment already flushed, so we need to schedule this segment to be emitted. // If it is a segment that was aborted, we'll write other content instead so we don't need // to emit it. - if (segment.status === COMPLETED) { + if (segment.status === COMPLETED || segment.status === ABORTED) { queueCompletedSegment(boundary, segment); } } @@ -5058,7 +5062,7 @@ function finishedTask( // Our parent already flushed, so we need to schedule this segment to be emitted. // If it is a segment that was aborted, we'll write other content instead so we don't need // to emit it. - if (segment.status === COMPLETED) { + if (segment.status === COMPLETED || segment.status === ABORTED) { queueCompletedSegment(boundary, segment); const completedSegments = boundary.completedSegments; if (completedSegments.length === 1) { @@ -5575,6 +5579,9 @@ function flushSubtree( } return r; } + case ABORTED: { + return true; + } default: { throw new Error( 'Aborted, errored or already flushed boundaries should not be flushed again. This is a bug in React.',