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.
Sophie Alpert committed
Mar 29, 2018 at 08:21 UTC
5855e9f2158b31d945f3fcc5bc582389dbecc88e
2 files changed
+29
-18
packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
+21
-13
@@ -244,10 +244,11 @@ describe('ReactCompositeComponent', () => {
244
ReactDOM.unmountComponentAtNode(container);
245
246
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 **)',
252
);
253
254
// No additional warning should be recorded
@@ -269,10 +270,15 @@ describe('ReactCompositeComponent', () => {
270
}
271
}
272
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
+ );
282
283
expect(renders).toBe(1);
284
@@ -280,15 +286,17 @@ describe('ReactCompositeComponent', () => {
286
287
expect(renders).toBe(2);
288
283
- ReactDOM.unmountComponentAtNode(container);
289
+ ReactDOM.render(<div />, container);
290
291
expect(() => {
292
instance.setState({value: 2});
293
}).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',
300
);
301
302
expect(renders).toBe(2);
packages/react-reconciler/src/ReactFiberScheduler.js
+8
-5
@@ -14,6 +14,7 @@ import type {HydrationContext} from './ReactFiberHydrationContext';
14
import type {ExpirationTime} from './ReactFiberExpirationTime';
15
16
import ReactErrorUtils from 'shared/ReactErrorUtils';
17
+import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentTreeHook';
18
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
19
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
20
import {
@@ -112,17 +113,19 @@ if (__DEV__) {
113
const didWarnStateUpdateForUnmountedComponent = {};
114
115
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.
118
const componentName = getComponentName(fiber) || 'ReactClass';
119
if (didWarnStateUpdateForUnmountedComponent[componentName]) {
120
return;
121
}
122
warning(
123
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),
129
);
130
didWarnStateUpdateForUnmountedComponent[componentName] = true;
131
};