main
js 193 lines 6.18 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Fiber, FiberRoot} from './ReactInternalTypes';
11 import type {CapturedValue} from './ReactCapturedValue';
12
13 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
14
15 import {ClassComponent} from './ReactWorkTags';
16
17 import reportGlobalError from 'shared/reportGlobalError';
18
19 import ReactSharedInternals from 'shared/ReactSharedInternals';
20
21 import {bindToConsole} from './ReactFiberConfig';
22
23 // Side-channel since I'm not sure we want to make this part of the public API
24 let componentName: null | string = null;
25 let errorBoundaryName: null | string = null;
26
27 export function defaultOnUncaughtError(
28 error: mixed,
29 errorInfo: {+componentStack?: ?string},
30 ): void {
31 // Overriding this can silence these warnings e.g. for tests.
32 // See https://github.com/facebook/react/pull/13384
33
34 // For uncaught root errors we report them as uncaught to the browser's
35 // onerror callback. This won't have component stacks and the error addendum.
36 // So we add those into a separate console.warn.
37 reportGlobalError(error);
38 if (__DEV__) {
39 const componentNameMessage = componentName
40 ? `An error occurred in the <${componentName}> component.`
41 : 'An error occurred in one of your React components.';
42
43 const errorBoundaryMessage =
44 'Consider adding an error boundary to your tree to customize error handling behavior.\n' +
45 'Visit https://react.dev/link/error-boundaries to learn more about error boundaries.';
46
47 try {
48 console.warn(
49 '%s\n\n%s\n',
50 componentNameMessage,
51 errorBoundaryMessage,
52 // We let our console.error wrapper add the component stack to the end.
53 );
54 } finally {
55 // ignore
56 }
57 }
58 }
59
60 export function defaultOnCaughtError(
61 error: mixed,
62 errorInfo: {
63 +componentStack?: ?string,
64 +errorBoundary?: ?component(...props: any),
65 },
66 ): void {
67 // Overriding this can silence these warnings e.g. for tests.
68 // See https://github.com/facebook/react/pull/13384
69
70 // Caught by error boundary
71 if (__DEV__) {
72 const componentNameMessage = componentName
73 ? `The above error occurred in the <${componentName}> component.`
74 : 'The above error occurred in one of your React components.';
75
76 // In development, we provide our own message which includes the component stack
77 // in addition to the error.
78 const recreateMessage =
79 `React will try to recreate this component tree from scratch ` +
80 `using the error boundary you provided, ${
81 errorBoundaryName || 'Anonymous'
82 }.`;
83
84 try {
85 if (
86 typeof error === 'object' &&
87 error !== null &&
88 typeof error.environmentName === 'string'
89 ) {
90 // This was a Server error. We print the environment name in a badge just like we do with
91 // replays of console logs to indicate that the source of this throw as actually the Server.
92 bindToConsole(
93 'error',
94 [
95 '%o\n\n%s\n\n%s\n',
96 error,
97 componentNameMessage,
98 recreateMessage,
99 // We let DevTools or console.createTask add the component stack to the end.
100 ],
101 error.environmentName,
102 )();
103 } else {
104 console.error(
105 '%o\n\n%s\n\n%s\n',
106 error,
107 componentNameMessage,
108 recreateMessage,
109 // We let our DevTools or console.createTask add the component stack to the end.
110 );
111 }
112 } finally {
113 // ignore
114 }
115 } else {
116 // In production, we print the error directly.
117 // This will include the message, the JS stack, and anything the browser wants to show.
118 // We pass the error object instead of custom message so that the browser displays the error natively.
119 console['error'](error); // Don't transform to our wrapper, however, React DevTools can still add a stack.
120 }
121 }
122
123 export function defaultOnRecoverableError(
124 error: mixed,
125 errorInfo: {+componentStack?: ?string},
126 ) {
127 reportGlobalError(error);
128 }
129
130 export function logUncaughtError(
131 root: FiberRoot,
132 errorInfo: CapturedValue<mixed>,
133 ): void {
134 try {
135 if (__DEV__) {
136 componentName = errorInfo.source
137 ? getComponentNameFromFiber(errorInfo.source)
138 : null;
139 errorBoundaryName = null;
140 }
141 const error = errorInfo.value as any;
142 if (__DEV__ && ReactSharedInternals.actQueue !== null) {
143 // For uncaught errors inside act, we track them on the act and then
144 // rethrow them into the test.
145 ReactSharedInternals.thrownErrors.push(error);
146 return;
147 }
148 const onUncaughtError = root.onUncaughtError;
149 onUncaughtError(error, {
150 componentStack: errorInfo.stack,
151 });
152 } catch (e) {
153 // This method must not throw, or React internal state will get messed up.
154 // If console.error is overridden, or logCapturedError() shows a dialog that throws,
155 // we want to report this error outside of the normal stack as a last resort.
156 // https://github.com/facebook/react/issues/13188
157 setTimeout(() => {
158 throw e;
159 });
160 }
161 }
162
163 export function logCaughtError(
164 root: FiberRoot,
165 boundary: Fiber,
166 errorInfo: CapturedValue<mixed>,
167 ): void {
168 try {
169 if (__DEV__) {
170 componentName = errorInfo.source
171 ? getComponentNameFromFiber(errorInfo.source)
172 : null;
173 errorBoundaryName = getComponentNameFromFiber(boundary);
174 }
175 const error = errorInfo.value as any;
176 const onCaughtError = root.onCaughtError;
177 onCaughtError(error, {
178 componentStack: errorInfo.stack,
179 errorBoundary:
180 boundary.tag === ClassComponent
181 ? boundary.stateNode // This should always be the case as long as we only have class boundaries
182 : null,
183 });
184 } catch (e) {
185 // This method must not throw, or React internal state will get messed up.
186 // If console.error is overridden, or logCapturedError() shows a dialog that throws,
187 // we want to report this error outside of the normal stack as a last resort.
188 // https://github.com/facebook/react/issues/13188
189 setTimeout(() => {
190 throw e;
191 });
192 }
193 }