diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index 0d901ecc780a0..f65a25a6d268e 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -786,7 +786,8 @@ describe('ReactDOMFizzServer', () => { errors, [ [ - theError.message, + 'Switched to client rendering because the server rendering errored:\n\n' + + theError.message, expectedDigest, componentStack(['Lazy', 'Suspense', 'div', 'App']), ], @@ -909,7 +910,8 @@ describe('ReactDOMFizzServer', () => { errors, [ [ - theError.message, + 'Switched to client rendering because the server rendering errored:\n\n' + + theError.message, expectedDigest, componentStack(['Lazy', 'Suspense', 'div', 'App']), ], @@ -992,7 +994,8 @@ describe('ReactDOMFizzServer', () => { errors, [ [ - theError.message, + 'Switched to client rendering because the server rendering errored:\n\n' + + theError.message, expectedDigest, componentStack([ 'Erroring', @@ -1078,7 +1081,8 @@ describe('ReactDOMFizzServer', () => { errors, [ [ - theError.message, + 'Switched to client rendering because the server rendering errored:\n\n' + + theError.message, expectedDigest, componentStack(['Lazy', 'Suspense', 'div', 'App']), ], @@ -1404,13 +1408,15 @@ describe('ReactDOMFizzServer', () => { errors, [ [ - 'The server did not finish this Suspense boundary: The render was aborted by the server without a reason.', + 'Switched to client rendering because the server rendering aborted due to:\n\n' + + 'The render was aborted by the server without a reason.', expectedDigest, // We get the stack of the task when it was aborted which is why we see `h1` componentStack(['h1', 'Suspense', 'div', 'App']), ], [ - 'The server did not finish this Suspense boundary: The render was aborted by the server without a reason.', + 'Switched to client rendering because the server rendering aborted due to:\n\n' + + 'The render was aborted by the server without a reason.', expectedDigest, componentStack(['Suspense', 'main', 'div', 'App']), ], @@ -2145,7 +2151,8 @@ describe('ReactDOMFizzServer', () => { errors, [ [ - theError.message, + 'Switched to client rendering because the server rendering errored:\n\n' + + theError.message, expectedDigest, componentStack([ 'AsyncText', @@ -3431,12 +3438,14 @@ describe('ReactDOMFizzServer', () => { errors, [ [ - 'The server did not finish this Suspense boundary: foobar', + 'Switched to client rendering because the server rendering aborted due to:\n\n' + + 'foobar', 'a digest', componentStack(['Suspense', 'p', 'div', 'App']), ], [ - 'The server did not finish this Suspense boundary: foobar', + 'Switched to client rendering because the server rendering aborted due to:\n\n' + + 'foobar', 'a digest', componentStack(['Suspense', 'span', 'div', 'App']), ], @@ -3512,12 +3521,14 @@ describe('ReactDOMFizzServer', () => { errors, [ [ - 'The server did not finish this Suspense boundary: uh oh', + 'Switched to client rendering because the server rendering aborted due to:\n\n' + + 'uh oh', 'a digest', componentStack(['Suspense', 'p', 'div', 'App']), ], [ - 'The server did not finish this Suspense boundary: uh oh', + 'Switched to client rendering because the server rendering aborted due to:\n\n' + + 'uh oh', 'a digest', componentStack(['Suspense', 'span', 'div', 'App']), ], @@ -3991,7 +4002,8 @@ describe('ReactDOMFizzServer', () => { errors, [ [ - theError.message, + 'Switched to client rendering because the server rendering errored:\n\n' + + theError.message, expectedDigest, componentStack(['Erroring', 'Suspense', 'div', 'App']), ], @@ -6772,7 +6784,14 @@ describe('ReactDOMFizzServer', () => { expect(recoverableErrors).toEqual( __DEV__ - ? ['server error', 'replay error', 'server error'] + ? [ + 'Switched to client rendering because the server rendering errored:\n\n' + + 'server error', + 'Switched to client rendering because the server rendering aborted due to:\n\n' + + 'replay error', + 'Switched to client rendering because the server rendering errored:\n\n' + + 'server error', + ] : [ 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', @@ -6931,8 +6950,10 @@ describe('ReactDOMFizzServer', () => { expect(recoverableErrors).toEqual( __DEV__ ? [ - 'The server did not finish this Suspense boundary: aborted', - 'The server did not finish this Suspense boundary: aborted', + 'Switched to client rendering because the server rendering aborted due to:\n\n' + + 'aborted', + 'Switched to client rendering because the server rendering aborted due to:\n\n' + + 'aborted', ] : [ 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', @@ -7103,8 +7124,10 @@ describe('ReactDOMFizzServer', () => { // It surfaced in two different suspense boundaries. __DEV__ ? [ - 'The server did not finish this Suspense boundary: replay error', - 'The server did not finish this Suspense boundary: replay error', + 'Switched to client rendering because the server rendering errored:\n\n' + + 'replay error', + 'Switched to client rendering because the server rendering errored:\n\n' + + 'replay error', ] : [ 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', @@ -7230,7 +7253,10 @@ describe('ReactDOMFizzServer', () => { expect(recoverableErrors).toEqual( __DEV__ - ? ['server error'] + ? [ + 'Switched to client rendering because the server rendering errored:\n\n' + + 'server error', + ] : [ 'The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering.', ], diff --git a/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js b/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js index b973f85211a0a..07cbcbf9bed34 100644 --- a/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMHydrationDiff-test.js @@ -1292,10 +1292,12 @@ describe('ReactDOMServerHydration', () => { } expect(testMismatch(Mismatch)).toMatchInlineSnapshot(` - [ - "Caught [The server did not finish this Suspense boundary: The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server]", - ] - `); + [ + "Caught [Switched to client rendering because the server rendering aborted due to: + + The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server]", + ] + `); }); // @gate __DEV__ @@ -1318,10 +1320,12 @@ describe('ReactDOMServerHydration', () => { } expect(testMismatch(Mismatch)).toMatchInlineSnapshot(` - [ - "Caught [The server did not finish this Suspense boundary: The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server]", - ] - `); + [ + "Caught [Switched to client rendering because the server rendering aborted due to: + + The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToPipeableStream" which supports Suspense on the server]", + ] + `); }); }); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index c702a81cb3933..e5f14af35552d 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -2659,18 +2659,18 @@ function updateDehydratedSuspenseComponent( // TODO: Figure out a better signal than encoding a magic digest value. if (!enablePostpone || digest !== 'POSTPONE') { let error; - if (message) { + if (__DEV__ && message) { // eslint-disable-next-line react-internal/prod-error-codes error = new Error(message); } else { error = new Error( 'The server could not finish this Suspense boundary, likely ' + - 'due to an error during server rendering. Switched to ' + - 'client rendering.', + 'due to an error during server rendering. ' + + 'Switched to client rendering.', ); } // Replace the stack with the server stack - error.stack = stack || ''; + error.stack = (__DEV__ && stack) || ''; (error: any).digest = digest; capturedValue = createCapturedValueFromError( error, diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 5d71c0cfc510e..a06ef06e8bb2b 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -820,6 +820,7 @@ function encodeErrorForBoundary( digest: ?string, error: mixed, thrownInfo: ThrownInfo, + wasAborted: boolean, ) { boundary.errorDigest = digest; if (__DEV__) { @@ -838,8 +839,10 @@ function encodeErrorForBoundary( message = String(error); stack = null; } - - boundary.errorMessage = message; + const prefix = wasAborted + ? 'Switched to client rendering because the server rendering aborted due to:\n\n' + : 'Switched to client rendering because the server rendering errored:\n\n'; + boundary.errorMessage = prefix + message; boundary.errorStack = stack; boundary.errorComponentStack = thrownInfo.componentStack; } @@ -1021,7 +1024,7 @@ function renderSuspenseBoundary( } else { errorDigest = logRecoverableError(request, error, thrownInfo); } - encodeErrorForBoundary(newBoundary, errorDigest, error, thrownInfo); + encodeErrorForBoundary(newBoundary, errorDigest, error, thrownInfo, false); untrackBoundary(request, newBoundary); @@ -1165,7 +1168,13 @@ function replaySuspenseBoundary( } else { errorDigest = logRecoverableError(request, error, thrownInfo); } - encodeErrorForBoundary(resumedBoundary, errorDigest, error, thrownInfo); + encodeErrorForBoundary( + resumedBoundary, + errorDigest, + error, + thrownInfo, + false, + ); task.replay.pendingTasks--; @@ -2962,6 +2971,7 @@ function erroredReplay( error, errorDigest, errorInfo, + false, ); } @@ -2992,7 +3002,7 @@ function erroredTask( boundary.pendingTasks--; if (boundary.status !== CLIENT_RENDERED) { boundary.status = CLIENT_RENDERED; - encodeErrorForBoundary(boundary, errorDigest, error, errorInfo); + encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, false); untrackBoundary(request, boundary); // Regardless of what happens next, this boundary won't be displayed, @@ -3032,6 +3042,7 @@ function abortRemainingSuspenseBoundary( error: mixed, errorDigest: ?string, errorInfo: ThrownInfo, + wasAborted: boolean, ): void { const resumedBoundary = createSuspenseBoundary(request, new Set()); resumedBoundary.parentFlushed = true; @@ -3039,17 +3050,13 @@ function abortRemainingSuspenseBoundary( resumedBoundary.rootSegmentID = rootSegmentID; resumedBoundary.status = CLIENT_RENDERED; - let errorMessage = error; - if (__DEV__) { - const errorPrefix = 'The server did not finish this Suspense boundary: '; - if (error && typeof error.message === 'string') { - errorMessage = errorPrefix + error.message; - } else { - // eslint-disable-next-line react-internal/safe-string-coercion - errorMessage = errorPrefix + String(error); - } - } - encodeErrorForBoundary(resumedBoundary, errorDigest, errorMessage, errorInfo); + encodeErrorForBoundary( + resumedBoundary, + errorDigest, + error, + errorInfo, + wasAborted, + ); if (resumedBoundary.parentFlushed) { request.clientRenderedBoundaries.push(resumedBoundary); @@ -3064,6 +3071,7 @@ function abortRemainingReplayNodes( error: mixed, errorDigest: ?string, errorInfo: ThrownInfo, + aborted: boolean, ): void { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; @@ -3076,6 +3084,7 @@ function abortRemainingReplayNodes( error, errorDigest, errorInfo, + aborted, ); } else { const boundaryNode: ReplaySuspenseBoundary = node; @@ -3086,6 +3095,7 @@ function abortRemainingReplayNodes( error, errorDigest, errorInfo, + aborted, ); } } @@ -3102,7 +3112,7 @@ function abortRemainingReplayNodes( ); } else if (boundary.status !== CLIENT_RENDERED) { boundary.status = CLIENT_RENDERED; - encodeErrorForBoundary(boundary, errorDigest, error, errorInfo); + encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, aborted); if (boundary.parentFlushed) { request.clientRenderedBoundaries.push(boundary); } @@ -3178,6 +3188,7 @@ function abortTask(task: Task, request: Request, error: mixed): void { error, errorDigest, errorInfo, + true, ); } request.pendingRootTasks--; @@ -3207,18 +3218,7 @@ function abortTask(task: Task, request: Request, error: mixed): void { } else { errorDigest = logRecoverableError(request, error, errorInfo); } - let errorMessage = error; - if (__DEV__) { - const errorPrefix = - 'The server did not finish this Suspense boundary: '; - if (error && typeof error.message === 'string') { - errorMessage = errorPrefix + error.message; - } else { - // eslint-disable-next-line react-internal/safe-string-coercion - errorMessage = errorPrefix + String(error); - } - } - encodeErrorForBoundary(boundary, errorDigest, errorMessage, errorInfo); + encodeErrorForBoundary(boundary, errorDigest, error, errorInfo, true); untrackBoundary(request, boundary);