@samitouri / QOS-React / commits / a1a6bc8974

[Fizz] Stop firing `onAllReady` after the shell errored (#36903)

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>

Sebastian "Sebbie" Silbermann committed Jul 1, 2026 at 12:07 UTC a1a6bc89742fbe8e1f9680a3051fdedc111fb378
2 files changed +21
packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
+17
@@ -210,6 +210,8 @@ describe('ReactDOMFizzServerNode', () => {
210 it('should error the stream when an error is thrown at the root', async () => {
211 const reportedErrors = [];
212 const reportedShellErrors = [];
213 + let shellReadyCalls = 0;
214 + let allReadyCalls = 0;
215 const {writable, output, completed} = getTestWritable();
216 const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
217 <div>
@@ -222,6 +224,12 @@ describe('ReactDOMFizzServerNode', () => {
224 onShellError(x) {
225 reportedShellErrors.push(x);
226 },
227 + onShellReady() {
228 + shellReadyCalls++;
229 + },
230 + onAllReady() {
231 + allReadyCalls++;
232 + },
233 },
234 );
235
@@ -235,6 +243,8 @@ describe('ReactDOMFizzServerNode', () => {
243 // This type of error is reported to the error callback too.
244 expect(reportedErrors).toEqual([theError]);
245 expect(reportedShellErrors).toEqual([theError]);
246 + expect(shellReadyCalls).toBe(0);
247 + expect(allReadyCalls).toBe(0);
248 });
249
250 it('should not report aborts after the shell has fatally errored', async () => {
@@ -301,6 +311,7 @@ describe('ReactDOMFizzServerNode', () => {
311 it('should not error the stream when an error is thrown inside suspense boundary', async () => {
312 const reportedErrors = [];
313 const reportedShellErrors = [];
314 + let allReadyCalls = 0;
315 const {writable, output, completed} = getTestWritable();
316 const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
317 <div>
@@ -315,6 +326,9 @@ describe('ReactDOMFizzServerNode', () => {
326 onShellError(x) {
327 reportedShellErrors.push(x);
328 },
329 + onAllReady() {
330 + allReadyCalls++;
331 + },
332 },
333 );
334 pipe(writable);
@@ -326,6 +340,9 @@ describe('ReactDOMFizzServerNode', () => {
340 // While no error is reported to the stream, the error is reported to the callback.
341 expect(reportedErrors).toEqual([theError]);
342 expect(reportedShellErrors).toEqual([]);
343 + // The shell stays valid, the boundary client-renders, and the render
344 + // completes, so onAllReady fires. This is documented behavior.
345 + expect(allReadyCalls).toBe(1);
346 });
347
348 it('should not attempt to render the fallback if the main content completes first', async () => {
packages/react-server/src/ReactFizzServer.js
+4
@@ -1378,6 +1378,10 @@ function fatalError(
1378 // It's also called if React itself or its host configs errors.
1379 const onShellError = request.onShellError;
1380 const onFatalError = request.onFatalError;
1381 + // The shell has fatally errored, so the render can never complete. Prevent a
1382 + // later completeAll from invoking onAllReady, which would signal a successful
1383 + // render to consumers waiting on all content.
1384 + request.onAllReady = noop;
1385 if (__DEV__ && debugTask) {
1386 debugTask.run(onShellError.bind(null, error));
1387 debugTask.run(onFatalError.bind(null, error));