From 2e470a788e359e34feeadb422daaff046baf66cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Wed, 14 Feb 2024 20:15:59 -0500 Subject: [PATCH] [Fizz] Align recoverable error serialization in dev mode (#28340) Same as #28327 but for Fizz. One thing that's weird about this recoverable error is that we don't send the regular stack for it, just the component stack it seems. This is missing some potential information and if we move toward integrated since stacks it would be one thing. --- .../src/__tests__/ReactFlight-test.js | 28 +++++++++---------- .../src/__tests__/ReactFlightDOM-test.js | 4 +-- .../__tests__/ReactFlightDOMBrowser-test.js | 2 +- packages/react-server/src/ReactFizzServer.js | 16 ++++++----- .../react-server/src/ReactFlightServer.js | 4 +-- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index f4a5929a2dd72..06dee1d54ed22 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -85,11 +85,11 @@ describe('ReactFlight', () => { ); let expectedDigest = this.props.expectedMessage; if ( - expectedDigest.startsWith('Error: {') || - expectedDigest.startsWith('Error: <') + expectedDigest.startsWith('{') || + expectedDigest.startsWith('<') ) { expectedDigest = '{}'; - } else if (expectedDigest.startsWith('Error: [')) { + } else if (expectedDigest.startsWith('[')) { expectedDigest = '[]'; } expect(this.state.error.digest).toContain(expectedDigest); @@ -799,12 +799,12 @@ describe('ReactFlight', () => { - +
- +
{
+ expectedMessage={'{message: "Short", extra: ..., nested: ...}'}>
{ />
- +
- +
- +
- +
} />
- +
- +
@@ -874,7 +872,7 @@ describe('ReactFlight', () => { } else if (typeof x === 'object' && x !== null) { return `digest({})`; } - return `digest(Error: ${String(x)})`; + return `digest(${String(x)})`; }, }); diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 91ba06dd329b9..465123f825500 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -919,9 +919,7 @@ describe('ReactFlightDOM', () => { abort('for reasons'); }); if (__DEV__) { - expect(container.innerHTML).toBe( - '

Error: for reasons + a dev digest

', - ); + expect(container.innerHTML).toBe('

for reasons + a dev digest

'); } else { expect(container.innerHTML).toBe('

digest("for reasons")

'); } diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index 2427317ce2c32..f8f3a1c3f2877 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -583,7 +583,7 @@ describe('ReactFlightDOMBrowser', () => { controller.abort('for reasons'); }); const expectedValue = __DEV__ - ? '

Error: for reasons + a dev digest

' + ? '

for reasons + a dev digest

' : '

digest("for reasons")

'; expect(container.innerHTML).toBe(expectedValue); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index a928b2651a934..e8ea394386df6 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -33,6 +33,7 @@ import type {ComponentStackNode} from './ReactFizzComponentStack'; import type {TreeContext} from './ReactFizzTreeContext'; import type {ThenableState} from './ReactFizzThenable'; import {enableRenderableContext} from 'shared/ReactFeatureFlags'; +import {describeObjectForErrorMessage} from 'shared/ReactSerializationErrors'; import { scheduleWork, @@ -816,18 +817,19 @@ function encodeErrorForBoundary( ) { boundary.errorDigest = digest; if (__DEV__) { + let message; // In dev we additionally encode the error message and component stack on the boundary - let errorMessage; - if (typeof error === 'string') { - errorMessage = error; - } else if (error && typeof error.message === 'string') { - errorMessage = error.message; + if (error instanceof Error) { + // eslint-disable-next-line react-internal/safe-string-coercion + message = String(error.message); + } else if (typeof error === 'object' && error !== null) { + message = describeObjectForErrorMessage(error); } else { // eslint-disable-next-line react-internal/safe-string-coercion - errorMessage = String(error); + message = String(error); } - boundary.errorMessage = errorMessage; + boundary.errorMessage = message; boundary.errorComponentStack = thrownInfo.componentStack; } } diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 4c81931223e46..072a71b03dcc5 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -1678,10 +1678,10 @@ function emitErrorChunk( // eslint-disable-next-line react-internal/safe-string-coercion stack = String(error.stack); } else if (typeof error === 'object' && error !== null) { - message = 'Error: ' + describeObjectForErrorMessage(error); + message = describeObjectForErrorMessage(error); } else { // eslint-disable-next-line react-internal/safe-string-coercion - message = 'Error: ' + String(error); + message = String(error); } } catch (x) { message = 'An error occurred but serializing the error message failed.';