Skip to content

Commit 9e9e397

Browse files
sophiebitsacdlite
authored andcommitted
Warn for Hook set-state on unmounted component
1 parent 6514697 commit 9e9e397

3 files changed

Lines changed: 45 additions & 10 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ describe('ReactCompositeComponent', () => {
332332
ReactDOM.unmountComponentAtNode(container);
333333

334334
expect(() => instance.forceUpdate()).toWarnDev(
335-
"Warning: Can't call setState (or forceUpdate) on an unmounted " +
335+
"Warning: Can't perform a React state update on an unmounted " +
336336
'component. This is a no-op, but it indicates a memory leak in your ' +
337337
'application. To fix, cancel all subscriptions and asynchronous ' +
338338
'tasks in the componentWillUnmount method.\n' +
@@ -379,7 +379,7 @@ describe('ReactCompositeComponent', () => {
379379
expect(() => {
380380
instance.setState({value: 2});
381381
}).toWarnDev(
382-
"Warning: Can't call setState (or forceUpdate) on an unmounted " +
382+
"Warning: Can't perform a React state update on an unmounted " +
383383
'component. This is a no-op, but it indicates a memory leak in your ' +
384384
'application. To fix, cancel all subscriptions and asynchronous ' +
385385
'tasks in the componentWillUnmount method.\n' +

packages/react-reconciler/src/ReactFiberScheduler.js

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,14 @@ import {
4545
Passive,
4646
} from 'shared/ReactSideEffectTags';
4747
import {
48-
HostRoot,
4948
ClassComponent,
5049
HostComponent,
5150
ContextProvider,
51+
ForwardRef,
52+
FunctionComponent,
5253
HostPortal,
54+
HostRoot,
55+
PureComponent,
5356
} from 'shared/ReactWorkTags';
5457
import {
5558
enableSchedulerTracing,
@@ -197,19 +200,21 @@ if (__DEV__) {
197200
didWarnSetStateChildContext = false;
198201
const didWarnStateUpdateForUnmountedComponent = {};
199202

200-
warnAboutUpdateOnUnmounted = function(fiber: Fiber) {
203+
warnAboutUpdateOnUnmounted = function(fiber: Fiber, isClass: boolean) {
201204
// We show the whole stack but dedupe on the top component's name because
202205
// the problematic code almost always lies inside that component.
203-
const componentName = getComponentName(fiber.type) || 'ReactClass';
206+
const componentName = getComponentName(fiber.type) || 'ReactComponent';
204207
if (didWarnStateUpdateForUnmountedComponent[componentName]) {
205208
return;
206209
}
207210
warningWithoutStack(
208211
false,
209-
"Can't call setState (or forceUpdate) on an unmounted component. This " +
212+
"Can't perform a React state update on an unmounted component. This " +
210213
'is a no-op, but it indicates a memory leak in your application. To ' +
211-
'fix, cancel all subscriptions and asynchronous tasks in the ' +
212-
'componentWillUnmount method.%s',
214+
'fix, cancel all subscriptions and asynchronous tasks in %s.%s',
215+
isClass
216+
? 'the componentWillUnmount method'
217+
: 'a useEffect cleanup function',
213218
ReactCurrentFiber.getStackByFiberInDevAndProd(fiber),
214219
);
215220
didWarnStateUpdateForUnmountedComponent[componentName] = true;
@@ -1785,8 +1790,17 @@ function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
17851790
}
17861791

17871792
if (root === null) {
1788-
if (__DEV__ && fiber.tag === ClassComponent) {
1789-
warnAboutUpdateOnUnmounted(fiber);
1793+
if (__DEV__) {
1794+
switch (fiber.tag) {
1795+
case ClassComponent:
1796+
warnAboutUpdateOnUnmounted(fiber, true);
1797+
break;
1798+
case FunctionComponent:
1799+
case ForwardRef:
1800+
case PureComponent:
1801+
warnAboutUpdateOnUnmounted(fiber, false);
1802+
break;
1803+
}
17901804
}
17911805
return null;
17921806
}

packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,27 @@ describe('ReactHooks', () => {
309309

310310
expect(updaters).toEqual([updaters[0], updaters[0], updaters[0]]);
311311
});
312+
313+
it('warns on set after unmount', () => {
314+
let _updateCount;
315+
function Counter(props, ref) {
316+
const [, updateCount] = useState(0);
317+
_updateCount = updateCount;
318+
return null;
319+
}
320+
321+
ReactNoop.render(<Counter />);
322+
ReactNoop.flush();
323+
ReactNoop.render(null);
324+
ReactNoop.flush();
325+
expect(() => _updateCount(1)).toWarnDev(
326+
"Warning: Can't perform a React state update on an unmounted " +
327+
'component. This is a no-op, but it indicates a memory leak in your ' +
328+
'application. To fix, cancel all subscriptions and asynchronous ' +
329+
'tasks in a useEffect cleanup function.\n' +
330+
' in Counter (at **)',
331+
);
332+
});
312333
});
313334

314335
describe('updates during the render phase', () => {

0 commit comments

Comments
 (0)