Skip to content

Commit 829401d

Browse files
authored
[Flight] Transport custom error names in dev mode (#32116)
Typed errors is not a feature that Flight currently supports. However, for presentation purposes, serializing a custom error name is something we could support today. With this PR, we're now transporting custom error names through the server-client boundary, so that they are available in the client e.g. for console replaying. One example where this can be useful is when you want to print debug information while leveraging the fact that `console.warn` displays the error stack, including handling of hiding and source mapping stack frames. In this case you may want to show `Warning: ...` or `Debug: ...` instead of `Error: ...`. In prod mode, we still transport an obfuscated error that uses the default `Error` name, to not leak any sensitive information from the server to the client. This also means that you must not rely on the error name to discriminate errors, e.g. when handling them in an error boundary.
1 parent fd2d279 commit 829401d

File tree

3 files changed

+24
-4
lines changed

3 files changed

+24
-4
lines changed

packages/react-client/src/ReactFlightClient.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -2123,8 +2123,15 @@ function resolveErrorProd(response: Response): Error {
21232123

21242124
function resolveErrorDev(
21252125
response: Response,
2126-
errorInfo: {message: string, stack: ReactStackTrace, env: string, ...},
2126+
errorInfo: {
2127+
name: string,
2128+
message: string,
2129+
stack: ReactStackTrace,
2130+
env: string,
2131+
...
2132+
},
21272133
): Error {
2134+
const name: string = errorInfo.name;
21282135
const message: string = errorInfo.message;
21292136
const stack: ReactStackTrace = errorInfo.stack;
21302137
const env: string = errorInfo.env;
@@ -2156,6 +2163,7 @@ function resolveErrorDev(
21562163
error = callStack();
21572164
}
21582165

2166+
(error: any).name = name;
21592167
(error: any).environmentName = env;
21602168
return error;
21612169
}

packages/react-client/src/__tests__/ReactFlight-test.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -694,9 +694,17 @@ describe('ReactFlight', () => {
694694
});
695695

696696
it('can transport Error objects as values', async () => {
697+
class CustomError extends Error {
698+
constructor(message) {
699+
super(message);
700+
this.name = 'Custom';
701+
}
702+
}
703+
697704
function ComponentClient({prop}) {
698705
return `
699706
is error: ${prop instanceof Error}
707+
name: ${prop.name}
700708
message: ${prop.message}
701709
stack: ${normalizeCodeLocInfo(prop.stack).split('\n').slice(0, 2).join('\n')}
702710
environmentName: ${prop.environmentName}
@@ -705,7 +713,7 @@ describe('ReactFlight', () => {
705713
const Component = clientReference(ComponentClient);
706714

707715
function ServerComponent() {
708-
const error = new Error('hello');
716+
const error = new CustomError('hello');
709717
return <Component prop={error} />;
710718
}
711719

@@ -718,14 +726,16 @@ describe('ReactFlight', () => {
718726
if (__DEV__) {
719727
expect(ReactNoop).toMatchRenderedOutput(`
720728
is error: true
729+
name: Custom
721730
message: hello
722-
stack: Error: hello
731+
stack: Custom: hello
723732
in ServerComponent (at **)
724733
environmentName: Server
725734
`);
726735
} else {
727736
expect(ReactNoop).toMatchRenderedOutput(`
728737
is error: true
738+
name: Error
729739
message: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.
730740
stack: Error: An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.
731741
environmentName: undefined

packages/react-server/src/ReactFlightServer.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -3093,10 +3093,12 @@ function emitPostponeChunk(
30933093

30943094
function serializeErrorValue(request: Request, error: Error): string {
30953095
if (__DEV__) {
3096+
let name;
30963097
let message;
30973098
let stack: ReactStackTrace;
30983099
let env = (0, request.environmentName)();
30993100
try {
3101+
name = error.name;
31003102
// eslint-disable-next-line react-internal/safe-string-coercion
31013103
message = String(error.message);
31023104
stack = filterStackTrace(request, error, 0);
@@ -3110,7 +3112,7 @@ function serializeErrorValue(request: Request, error: Error): string {
31103112
message = 'An error occurred but serializing the error message failed.';
31113113
stack = [];
31123114
}
3113-
const errorInfo = {message, stack, env};
3115+
const errorInfo = {name, message, stack, env};
31143116
const id = outlineModel(request, errorInfo);
31153117
return '$Z' + id.toString(16);
31163118
} else {

0 commit comments

Comments
 (0)