@samitouri / QOS-React / commits / 2e470a788e

[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.

Sebastian Markbåge committed Feb 14, 2024 at 20:15 UTC 2e470a788e359e34feeadb422daaff046baf66cc
5 files changed +26 -28
packages/react-client/src/__tests__/ReactFlight-test.js
+13 -15
@@ -85,11 +85,11 @@ describe('ReactFlight', () => {
85 );
86 let expectedDigest = this.props.expectedMessage;
87 if (
88 - expectedDigest.startsWith('Error: {') ||
89 - expectedDigest.startsWith('Error: <')
88 + expectedDigest.startsWith('{') ||
89 + expectedDigest.startsWith('<')
90 ) {
91 expectedDigest = '{}';
92 - } else if (expectedDigest.startsWith('Error: [')) {
92 + } else if (expectedDigest.startsWith('[')) {
93 expectedDigest = '[]';
94 }
95 expect(this.state.error.digest).toContain(expectedDigest);
@@ -799,12 +799,12 @@ describe('ReactFlight', () => {
799 <Throw value={new TypeError('This is a real Error.')} />
800 </div>
801 </ClientErrorBoundary>
802 - <ClientErrorBoundary expectedMessage="Error: This is a string error.">
802 + <ClientErrorBoundary expectedMessage="This is a string error.">
803 <div>
804 <Throw value="This is a string error." />
805 </div>
806 </ClientErrorBoundary>
807 - <ClientErrorBoundary expectedMessage="Error: {message: ..., extra: ..., nested: ...}">
807 + <ClientErrorBoundary expectedMessage="{message: ..., extra: ..., nested: ...}">
808 <div>
809 <Throw
810 value={{
@@ -816,9 +816,7 @@ describe('ReactFlight', () => {
816 </div>
817 </ClientErrorBoundary>
818 <ClientErrorBoundary
819 - expectedMessage={
820 - 'Error: {message: "Short", extra: ..., nested: ...}'
821 - }>
819 + expectedMessage={'{message: "Short", extra: ..., nested: ...}'}>
820 <div>
821 <Throw
822 value={{
@@ -829,32 +827,32 @@ describe('ReactFlight', () => {
827 />
828 </div>
829 </ClientErrorBoundary>
832 - <ClientErrorBoundary expectedMessage="Error: Symbol(hello)">
830 + <ClientErrorBoundary expectedMessage="Symbol(hello)">
831 <div>
832 <Throw value={Symbol('hello')} />
833 </div>
834 </ClientErrorBoundary>
837 - <ClientErrorBoundary expectedMessage="Error: 123">
835 + <ClientErrorBoundary expectedMessage="123">
836 <div>
837 <Throw value={123} />
838 </div>
839 </ClientErrorBoundary>
842 - <ClientErrorBoundary expectedMessage="Error: undefined">
840 + <ClientErrorBoundary expectedMessage="undefined">
841 <div>
842 <Throw value={undefined} />
843 </div>
844 </ClientErrorBoundary>
847 - <ClientErrorBoundary expectedMessage="Error: <div/>">
845 + <ClientErrorBoundary expectedMessage="<div/>">
846 <div>
847 <Throw value={<div />} />
848 </div>
849 </ClientErrorBoundary>
852 - <ClientErrorBoundary expectedMessage="Error: function Foo() {}">
850 + <ClientErrorBoundary expectedMessage="function Foo() {}">
851 <div>
852 <Throw value={function Foo() {}} />
853 </div>
854 </ClientErrorBoundary>
857 - <ClientErrorBoundary expectedMessage={'Error: ["array"]'}>
855 + <ClientErrorBoundary expectedMessage={'["array"]'}>
856 <div>
857 <Throw value={['array']} />
858 </div>
@@ -874,7 +872,7 @@ describe('ReactFlight', () => {
872 } else if (typeof x === 'object' && x !== null) {
873 return `digest({})`;
874 }
877 - return `digest(Error: ${String(x)})`;
875 + return `digest(${String(x)})`;
876 },
877 });
878
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+1 -3
@@ -919,9 +919,7 @@ describe('ReactFlightDOM', () => {
919 abort('for reasons');
920 });
921 if (__DEV__) {
922 - expect(container.innerHTML).toBe(
923 - '<p>Error: for reasons + a dev digest</p>',
924 - );
922 + expect(container.innerHTML).toBe('<p>for reasons + a dev digest</p>');
923 } else {
924 expect(container.innerHTML).toBe('<p>digest("for reasons")</p>');
925 }
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+1 -1
@@ -583,7 +583,7 @@ describe('ReactFlightDOMBrowser', () => {
583 controller.abort('for reasons');
584 });
585 const expectedValue = __DEV__
586 - ? '<p>Error: for reasons + a dev digest</p>'
586 + ? '<p>for reasons + a dev digest</p>'
587 : '<p>digest("for reasons")</p>';
588 expect(container.innerHTML).toBe(expectedValue);
589
packages/react-server/src/ReactFizzServer.js
+9 -7
@@ -33,6 +33,7 @@ import type {ComponentStackNode} from './ReactFizzComponentStack';
33 import type {TreeContext} from './ReactFizzTreeContext';
34 import type {ThenableState} from './ReactFizzThenable';
35 import {enableRenderableContext} from 'shared/ReactFeatureFlags';
36 +import {describeObjectForErrorMessage} from 'shared/ReactSerializationErrors';
37
38 import {
39 scheduleWork,
@@ -816,18 +817,19 @@ function encodeErrorForBoundary(
817 ) {
818 boundary.errorDigest = digest;
819 if (__DEV__) {
820 + let message;
821 // In dev we additionally encode the error message and component stack on the boundary
820 - let errorMessage;
821 - if (typeof error === 'string') {
822 - errorMessage = error;
823 - } else if (error && typeof error.message === 'string') {
824 - errorMessage = error.message;
822 + if (error instanceof Error) {
823 + // eslint-disable-next-line react-internal/safe-string-coercion
824 + message = String(error.message);
825 + } else if (typeof error === 'object' && error !== null) {
826 + message = describeObjectForErrorMessage(error);
827 } else {
828 // eslint-disable-next-line react-internal/safe-string-coercion
827 - errorMessage = String(error);
829 + message = String(error);
830 }
831
830 - boundary.errorMessage = errorMessage;
832 + boundary.errorMessage = message;
833 boundary.errorComponentStack = thrownInfo.componentStack;
834 }
835 }
packages/react-server/src/ReactFlightServer.js
+2 -2
@@ -1678,10 +1678,10 @@ function emitErrorChunk(
1678 // eslint-disable-next-line react-internal/safe-string-coercion
1679 stack = String(error.stack);
1680 } else if (typeof error === 'object' && error !== null) {
1681 - message = 'Error: ' + describeObjectForErrorMessage(error);
1681 + message = describeObjectForErrorMessage(error);
1682 } else {
1683 // eslint-disable-next-line react-internal/safe-string-coercion
1684 - message = 'Error: ' + String(error);
1684 + message = String(error);
1685 }
1686 } catch (x) {
1687 message = 'An error occurred but serializing the error message failed.';