diff --git a/packages/react-reconciler/src/ReactFiberThrow.js b/packages/react-reconciler/src/ReactFiberThrow.js index f56b1a04b33a..e3f6ba0cd813 100644 --- a/packages/react-reconciler/src/ReactFiberThrow.js +++ b/packages/react-reconciler/src/ReactFiberThrow.js @@ -87,16 +87,21 @@ function createClassErrorUpdate( ): Update { const update = createUpdate(expirationTime, null); update.tag = CaptureUpdate; + const inst = fiber.stateNode; const getDerivedStateFromError = fiber.type.getDerivedStateFromError; if (typeof getDerivedStateFromError === 'function') { const error = errorInfo.value; + const stack = errorInfo.stack; + const errorPartialInfo = { + componentStack: stack !== null ? stack : '', + }; + const state = inst !== null ? inst.state : null; update.payload = () => { logError(fiber, errorInfo); - return getDerivedStateFromError(error); + return getDerivedStateFromError(error, errorPartialInfo, state); }; } - const inst = fiber.stateNode; if (inst !== null && typeof inst.componentDidCatch === 'function') { update.callback = function callback() { if (__DEV__) { @@ -115,9 +120,10 @@ function createClassErrorUpdate( } const error = errorInfo.value; const stack = errorInfo.stack; - this.componentDidCatch(error, { + const errorPartialInfo = { componentStack: stack !== null ? stack : '', - }); + }; + this.componentDidCatch(error, errorPartialInfo); if (__DEV__) { if (typeof getDerivedStateFromError !== 'function') { // If componentDidCatch is the only error boundary method defined, diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js index 519c2d3f3a85..ad5ed929dce7 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js @@ -1600,16 +1600,23 @@ describe('ReactIncrementalErrorHandling', () => { ]); }); - it('does not provide component stack to the error boundary with getDerivedStateFromError', () => { + it('provides component stack and state to the error boundary with getDerivedStateFromError', () => { class ErrorBoundary extends React.Component { - state = {error: null}; - static getDerivedStateFromError(error, errorInfo) { - expect(errorInfo).toBeUndefined(); - return {error}; + state = {error: null, foo: 'foo'}; + static getDerivedStateFromError(error, errorInfo, prevState) { + expect(error.message).toBe('Hello'); + expect(prevState.foo).toBe('foo'); + return {error, errorInfo, foo: prevState.foo.toUpperCase()}; } render() { - if (this.state.error) { - return ; + if (this.state.errorInfo) { + return ( + + ); } return this.props.children; } @@ -1625,7 +1632,18 @@ describe('ReactIncrementalErrorHandling', () => { , ); expect(Scheduler).toFlushWithoutYielding(); - expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]); + expect(ReactNoop.getChildren()).toEqual([ + span( + 'Caught an error:\n' + + (__DEV__ + ? ' in BrokenRender (at **)\n' + : ' in BrokenRender\n') + + (__DEV__ + ? ' in ErrorBoundary (at **).' + : ' in ErrorBoundary.') + + ' Derived foo: FOO.', + ), + ]); }); it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', () => {