@samitouri / QOS-React-2 / commits / 20da1dae4b

Fix error logging in getDerivedStateFromProps (#15797)

* Fix error logging in getDerivedStateFromProps * Update tests, don't log for both error boundary methods * Re-add change lost in rebase

Ricky committed Jun 25, 2019 at 18:02 UTC 20da1dae4b9523ee94dc67797b11ec789e3acc68
4 files changed +130 -1
packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
+33
@@ -644,6 +644,39 @@ describe('ReactErrorBoundaries', () => {
644 expect(container3.firstChild).toBe(null);
645 });
646
647 + it('logs a single error when using error boundary', () => {
648 + const container = document.createElement('div');
649 + expect(() =>
650 + ReactDOM.render(
651 + <ErrorBoundary>
652 + <BrokenRender />
653 + </ErrorBoundary>,
654 + container,
655 + ),
656 + ).toWarnDev('The above error occurred in the <BrokenRender> component:', {
657 + logAllErrors: true,
658 + });
659 +
660 + expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
661 + expect(log).toEqual([
662 + 'ErrorBoundary constructor',
663 + 'ErrorBoundary componentWillMount',
664 + 'ErrorBoundary render success',
665 + 'BrokenRender constructor',
666 + 'BrokenRender componentWillMount',
667 + 'BrokenRender render [!]',
668 + // Catch and render an error message
669 + 'ErrorBoundary static getDerivedStateFromError',
670 + 'ErrorBoundary componentWillMount',
671 + 'ErrorBoundary render error',
672 + 'ErrorBoundary componentDidMount',
673 + ]);
674 +
675 + log.length = 0;
676 + ReactDOM.unmountComponentAtNode(container);
677 + expect(log).toEqual(['ErrorBoundary componentWillUnmount']);
678 + });
679 +
680 it('renders an error state if child throws in render', () => {
681 const container = document.createElement('div');
682 ReactDOM.render(
packages/react-dom/src/__tests__/ReactLegacyErrorBoundaries-test.internal.js
+91
@@ -30,6 +30,7 @@ describe('ReactLegacyErrorBoundaries', () => {
30 let BrokenComponentDidMountErrorBoundary;
31 let BrokenRender;
32 let ErrorBoundary;
33 + let BothErrorBoundaries;
34 let ErrorMessage;
35 let NoopErrorBoundary;
36 let RetryErrorBoundary;
@@ -486,6 +487,57 @@ describe('ReactLegacyErrorBoundaries', () => {
487 },
488 };
489
490 + BothErrorBoundaries = class extends React.Component {
491 + constructor(props) {
492 + super(props);
493 + this.state = {error: null};
494 + log.push('BothErrorBoundaries constructor');
495 + }
496 +
497 + static getDerivedStateFromError(error) {
498 + log.push('BothErrorBoundaries static getDerivedStateFromError');
499 + return {error};
500 + }
501 +
502 + render() {
503 + if (this.state.error) {
504 + log.push('BothErrorBoundaries render error');
505 + return <div>Caught an error: {this.state.error.message}.</div>;
506 + }
507 + log.push('BothErrorBoundaries render success');
508 + return <div>{this.props.children}</div>;
509 + }
510 +
511 + componentDidCatch(error) {
512 + log.push('BothErrorBoundaries componentDidCatch');
513 + this.setState({error});
514 + }
515 +
516 + UNSAFE_componentWillMount() {
517 + log.push('BothErrorBoundaries componentWillMount');
518 + }
519 +
520 + componentDidMount() {
521 + log.push('BothErrorBoundaries componentDidMount');
522 + }
523 +
524 + UNSAFE_componentWillReceiveProps() {
525 + log.push('BothErrorBoundaries componentWillReceiveProps');
526 + }
527 +
528 + UNSAFE_componentWillUpdate() {
529 + log.push('BothErrorBoundaries componentWillUpdate');
530 + }
531 +
532 + componentDidUpdate() {
533 + log.push('BothErrorBoundaries componentDidUpdate');
534 + }
535 +
536 + componentWillUnmount() {
537 + log.push('BothErrorBoundaries componentWillUnmount');
538 + }
539 + };
540 +
541 RetryErrorBoundary = class extends React.Component {
542 constructor(props) {
543 super(props);
@@ -614,6 +666,45 @@ describe('ReactLegacyErrorBoundaries', () => {
666 expect(container3.firstChild).toBe(null);
667 });
668
669 + it('logs a single error using both error boundaries', () => {
670 + const container = document.createElement('div');
671 + expect(() =>
672 + ReactDOM.render(
673 + <BothErrorBoundaries>
674 + <BrokenRender />
675 + </BothErrorBoundaries>,
676 + container,
677 + ),
678 + ).toWarnDev('The above error occurred in the <BrokenRender> component', {
679 + logAllErrors: true,
680 + });
681 +
682 + expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
683 + expect(log).toEqual([
684 + 'BothErrorBoundaries constructor',
685 + 'BothErrorBoundaries componentWillMount',
686 + 'BothErrorBoundaries render success',
687 + 'BrokenRender constructor',
688 + 'BrokenRender componentWillMount',
689 + 'BrokenRender render [!]',
690 + // Both getDerivedStateFromError and componentDidCatch should be called
691 + 'BothErrorBoundaries static getDerivedStateFromError',
692 + 'BothErrorBoundaries componentWillMount',
693 + 'BothErrorBoundaries render error',
694 + // Fiber mounts with null children before capturing error
695 + 'BothErrorBoundaries componentDidMount',
696 + // Catch and render an error message
697 + 'BothErrorBoundaries componentDidCatch',
698 + 'BothErrorBoundaries componentWillUpdate',
699 + 'BothErrorBoundaries render error',
700 + 'BothErrorBoundaries componentDidUpdate',
701 + ]);
702 +
703 + log.length = 0;
704 + ReactDOM.unmountComponentAtNode(container);
705 + expect(log).toEqual(['BothErrorBoundaries componentWillUnmount']);
706 + });
707 +
708 it('renders an error state if child throws in render', () => {
709 const container = document.createElement('div');
710 ReactDOM.render(
packages/react-reconciler/src/ReactFiberThrow.js
+4 -1
@@ -102,6 +102,7 @@ function createClassErrorUpdate(
102 if (typeof getDerivedStateFromError === 'function') {
103 const error = errorInfo.value;
104 update.payload = () => {
105 + logError(fiber, errorInfo);
106 return getDerivedStateFromError(error);
107 };
108 }
@@ -119,10 +120,12 @@ function createClassErrorUpdate(
120 // TODO: Warn in strict mode if getDerivedStateFromError is
121 // not defined.
122 markLegacyErrorBoundaryAsFailed(this);
123 +
124 + // Only log here if componentDidCatch is the only error boundary method defined
125 + logError(fiber, errorInfo);
126 }
127 const error = errorInfo.value;
128 const stack = errorInfo.stack;
125 - logError(fiber, errorInfo);
129 this.componentDidCatch(error, {
130 componentStack: stack !== null ? stack : '',
131 });
scripts/jest/matchers/toWarnDev.js
+2
@@ -38,6 +38,7 @@ const createMatcherFor = consoleMethod =>
38 }
39
40 const withoutStack = options.withoutStack;
41 + const logAllErrors = options.logAllErrors;
42 const warningsWithoutComponentStack = [];
43 const warningsWithComponentStack = [];
44 const unexpectedWarnings = [];
@@ -58,6 +59,7 @@ const createMatcherFor = consoleMethod =>
59 // Ignore uncaught errors reported by jsdom
60 // and React addendums because they're too noisy.
61 if (
62 + !logAllErrors &&
63 consoleMethod === 'error' &&
64 shouldIgnoreConsoleError(format, args)
65 ) {