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