Skip to content

Commit 5855e9f

Browse files
sophiebitsgaearon
authored andcommitted
Improve warning message for setState-on-unmounted (#12347)
This is one of the most common warnings people see, and I don't think the old text is especially clear. Improve it.
1 parent 7a833da commit 5855e9f

File tree

2 files changed

+29
-18
lines changed

2 files changed

+29
-18
lines changed

packages/react-dom/src/__tests__/ReactCompositeComponent-test.js

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,11 @@ describe('ReactCompositeComponent', () => {
244244
ReactDOM.unmountComponentAtNode(container);
245245

246246
expect(() => instance.forceUpdate()).toWarnDev(
247-
'Can only update a mounted or mounting component. This usually means ' +
248-
'you called setState, replaceState, or forceUpdate on an unmounted ' +
249-
'component. This is a no-op.\n\nPlease check the code for the ' +
250-
'Component component.',
247+
"Warning: Can't call setState (or forceUpdate) on an unmounted " +
248+
'component. This is a no-op, but it indicates a memory leak in your ' +
249+
'application. To fix, cancel all subscriptions and asynchronous ' +
250+
'tasks in the componentWillUnmount method.\n' +
251+
' in Component (at **)',
251252
);
252253

253254
// No additional warning should be recorded
@@ -269,26 +270,33 @@ describe('ReactCompositeComponent', () => {
269270
}
270271
}
271272

272-
let instance = <Component />;
273-
expect(instance.setState).not.toBeDefined();
274-
275-
instance = ReactDOM.render(instance, container);
273+
let instance;
274+
ReactDOM.render(
275+
<div>
276+
<span>
277+
<Component ref={c => (instance = c || instance)} />
278+
</span>
279+
</div>,
280+
container,
281+
);
276282

277283
expect(renders).toBe(1);
278284

279285
instance.setState({value: 1});
280286

281287
expect(renders).toBe(2);
282288

283-
ReactDOM.unmountComponentAtNode(container);
289+
ReactDOM.render(<div />, container);
284290

285291
expect(() => {
286292
instance.setState({value: 2});
287293
}).toWarnDev(
288-
'Can only update a mounted or mounting component. This usually means ' +
289-
'you called setState, replaceState, or forceUpdate on an unmounted ' +
290-
'component. This is a no-op.\n\nPlease check the code for the ' +
291-
'Component component.',
294+
"Warning: Can't call setState (or forceUpdate) on an unmounted " +
295+
'component. This is a no-op, but it indicates a memory leak in your ' +
296+
'application. To fix, cancel all subscriptions and asynchronous ' +
297+
'tasks in the componentWillUnmount method.\n' +
298+
' in Component (at **)\n' +
299+
' in span',
292300
);
293301

294302
expect(renders).toBe(2);

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {HydrationContext} from './ReactFiberHydrationContext';
1414
import type {ExpirationTime} from './ReactFiberExpirationTime';
1515

1616
import ReactErrorUtils from 'shared/ReactErrorUtils';
17+
import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
1718
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
1819
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
1920
import {
@@ -112,17 +113,19 @@ if (__DEV__) {
112113
const didWarnStateUpdateForUnmountedComponent = {};
113114

114115
warnAboutUpdateOnUnmounted = function(fiber: Fiber) {
116+
// We show the whole stack but dedupe on the top component's name because
117+
// the problematic code almost always lies inside that component.
115118
const componentName = getComponentName(fiber) || 'ReactClass';
116119
if (didWarnStateUpdateForUnmountedComponent[componentName]) {
117120
return;
118121
}
119122
warning(
120123
false,
121-
'Can only update a mounted or mounting ' +
122-
'component. This usually means you called setState, replaceState, ' +
123-
'or forceUpdate on an unmounted component. This is a no-op.\n\nPlease ' +
124-
'check the code for the %s component.',
125-
componentName,
124+
"Can't call setState (or forceUpdate) on an unmounted component. This " +
125+
'is a no-op, but it indicates a memory leak in your application. To ' +
126+
'fix, cancel all subscriptions and asynchronous tasks in the ' +
127+
'componentWillUnmount method.%s',
128+
getStackAddendumByWorkInProgressFiber(fiber),
126129
);
127130
didWarnStateUpdateForUnmountedComponent[componentName] = true;
128131
};

0 commit comments

Comments
 (0)