[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));