diff --git a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js index 54649cc28f224..10f63f6ac1fa3 100644 --- a/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactCompositeComponent-test.js @@ -244,10 +244,11 @@ describe('ReactCompositeComponent', () => { ReactDOM.unmountComponentAtNode(container); expect(() => instance.forceUpdate()).toWarnDev( - 'Can only update a mounted or mounting component. This usually means ' + - 'you called setState, replaceState, or forceUpdate on an unmounted ' + - 'component. This is a no-op.\n\nPlease check the code for the ' + - 'Component component.', + "Warning: Can't call setState (or forceUpdate) on an unmounted " + + 'component. This is a no-op, but it indicates a memory leak in your ' + + 'application. To fix, cancel all subscriptions and asynchronous ' + + 'tasks in the componentWillUnmount method.\n' + + ' in Component (at **)', ); // No additional warning should be recorded @@ -269,10 +270,15 @@ describe('ReactCompositeComponent', () => { } } - let instance = ; - expect(instance.setState).not.toBeDefined(); - - instance = ReactDOM.render(instance, container); + let instance; + ReactDOM.render( +
+ + (instance = c || instance)} /> + +
, + container, + ); expect(renders).toBe(1); @@ -280,15 +286,17 @@ describe('ReactCompositeComponent', () => { expect(renders).toBe(2); - ReactDOM.unmountComponentAtNode(container); + ReactDOM.render(
, container); expect(() => { instance.setState({value: 2}); }).toWarnDev( - 'Can only update a mounted or mounting component. This usually means ' + - 'you called setState, replaceState, or forceUpdate on an unmounted ' + - 'component. This is a no-op.\n\nPlease check the code for the ' + - 'Component component.', + "Warning: Can't call setState (or forceUpdate) on an unmounted " + + 'component. This is a no-op, but it indicates a memory leak in your ' + + 'application. To fix, cancel all subscriptions and asynchronous ' + + 'tasks in the componentWillUnmount method.\n' + + ' in Component (at **)\n' + + ' in span', ); expect(renders).toBe(2); diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 1aaa20a4c03f7..dcd5dcd76f243 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -14,6 +14,7 @@ import type {HydrationContext} from './ReactFiberHydrationContext'; import type {ExpirationTime} from './ReactFiberExpirationTime'; import ReactErrorUtils from 'shared/ReactErrorUtils'; +import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook'; import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState'; import ReactStrictModeWarnings from './ReactStrictModeWarnings'; import { @@ -112,17 +113,19 @@ if (__DEV__) { const didWarnStateUpdateForUnmountedComponent = {}; warnAboutUpdateOnUnmounted = function(fiber: Fiber) { + // We show the whole stack but dedupe on the top component's name because + // the problematic code almost always lies inside that component. const componentName = getComponentName(fiber) || 'ReactClass'; if (didWarnStateUpdateForUnmountedComponent[componentName]) { return; } warning( false, - 'Can only update a mounted or mounting ' + - 'component. This usually means you called setState, replaceState, ' + - 'or forceUpdate on an unmounted component. This is a no-op.\n\nPlease ' + - 'check the code for the %s component.', - componentName, + "Can't call setState (or forceUpdate) on an unmounted component. This " + + 'is a no-op, but it indicates a memory leak in your application. To ' + + 'fix, cancel all subscriptions and asynchronous tasks in the ' + + 'componentWillUnmount method.%s', + getStackAddendumByWorkInProgressFiber(fiber), ); didWarnStateUpdateForUnmountedComponent[componentName] = true; };