Fix now-missing errorInfo argument to componentDidCatch() (#12416)
* Add a failing test verifying componentInfo is missing * Pass componentInfo to componentDidCatch and getDerivedStateFromCatch * Only expect stack in DEV * Don't pass the stack to getDerivedStateFromCatch()
Dan Abramov committed
Mar 21, 2018 at 16:38 UTC
3553489f7b26fc3cb952321dd21c0b828a375f55
2 files changed
+78
-1
packages/react-reconciler/src/ReactFiberCommitWork.js
+4
-1
@@ -270,8 +270,11 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
270
for (let i = 0; i < capturedErrors.length; i++) {
271
const errorInfo = capturedErrors[i];
272
const error = errorInfo.value;
273
+ const stack = errorInfo.stack;
274
logError(finishedWork, errorInfo);
274
- instance.componentDidCatch(error);
275
+ instance.componentDidCatch(error, {
276
+ componentStack: stack !== null ? stack : '',
277
+ });
278
}
279
}
280
break;
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+74
@@ -36,6 +36,10 @@ describe('ReactIncrementalErrorHandling', () => {
36
return {type: 'span', children: [], prop};
37
}
38
39
+ function normalizeCodeLocInfo(str) {
40
+ return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
41
+ }
42
+
43
it('recovers from errors asynchronously', () => {
44
class ErrorBoundary extends React.Component {
45
state = {error: null};
@@ -1302,4 +1306,74 @@ describe('ReactIncrementalErrorHandling', () => {
1306
]);
1307
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: oops!')]);
1308
});
1309
+
1310
+ it('provides component stack to the error boundary with componentDidCatch', () => {
1311
+ class ErrorBoundary extends React.Component {
1312
+ state = {error: null, errorInfo: null};
1313
+ componentDidCatch(error, errorInfo) {
1314
+ this.setState({error, errorInfo});
1315
+ }
1316
+ render() {
1317
+ if (this.state.errorInfo) {
1318
+ ReactNoop.yield('render error message');
1319
+ return (
1320
+ <span
1321
+ prop={`Caught an error:${normalizeCodeLocInfo(
1322
+ this.state.errorInfo.componentStack,
1323
+ )}.`}
1324
+ />
1325
+ );
1326
+ }
1327
+ return this.props.children;
1328
+ }
1329
+ }
1330
+
1331
+ function BrokenRender(props) {
1332
+ throw new Error('Hello');
1333
+ }
1334
+
1335
+ ReactNoop.render(
1336
+ <ErrorBoundary>
1337
+ <BrokenRender />
1338
+ </ErrorBoundary>,
1339
+ );
1340
+ ReactNoop.flushDeferredPri();
1341
+ expect(ReactNoop.getChildren()).toEqual([
1342
+ span(
1343
+ 'Caught an error:\n' +
1344
+ (__DEV__
1345
+ ? ' in BrokenRender (at **)\n'
1346
+ : ' in BrokenRender\n') +
1347
+ (__DEV__ ? ' in ErrorBoundary (at **).' : ' in ErrorBoundary.'),
1348
+ ),
1349
+ ]);
1350
+ });
1351
+
1352
+ it('does not provide component stack to the error boundary with getDerivedStateFromCatch', () => {
1353
+ class ErrorBoundary extends React.Component {
1354
+ state = {error: null};
1355
+ static getDerivedStateFromCatch(error, errorInfo) {
1356
+ expect(errorInfo).toBeUndefined();
1357
+ return {error};
1358
+ }
1359
+ render() {
1360
+ if (this.state.error) {
1361
+ return <span prop={`Caught an error: ${this.state.error.message}`} />;
1362
+ }
1363
+ return this.props.children;
1364
+ }
1365
+ }
1366
+
1367
+ function BrokenRender(props) {
1368
+ throw new Error('Hello');
1369
+ }
1370
+
1371
+ ReactNoop.render(
1372
+ <ErrorBoundary>
1373
+ <BrokenRender />
1374
+ </ErrorBoundary>,
1375
+ );
1376
+ ReactNoop.flushDeferredPri();
1377
+ expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]);
1378
+ });
1379
});