Skip to content

Commit 7ed0706

Browse files
authored
Remove the warning for setState on unmounted components (#22114)
* Remove warning for setState on unmounted components * Trigger CI
1 parent 6abda7f commit 7ed0706

File tree

4 files changed

+12
-291
lines changed

4 files changed

+12
-291
lines changed

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

+4-24
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ describe('ReactCompositeComponent', () => {
307307
ReactDOM.render(<MyComponent />, container2);
308308
});
309309

310-
it('should warn about `forceUpdate` on unmounted components', () => {
310+
it('should not warn about `forceUpdate` on unmounted components', () => {
311311
const container = document.createElement('div');
312312
document.body.appendChild(container);
313313

@@ -325,19 +325,11 @@ describe('ReactCompositeComponent', () => {
325325

326326
ReactDOM.unmountComponentAtNode(container);
327327

328-
expect(() => instance.forceUpdate()).toErrorDev(
329-
"Warning: Can't perform a React state update on an unmounted " +
330-
'component. This is a no-op, but it indicates a memory leak in your ' +
331-
'application. To fix, cancel all subscriptions and asynchronous ' +
332-
'tasks in the componentWillUnmount method.\n' +
333-
' in Component (at **)',
334-
);
335-
336-
// No additional warning should be recorded
328+
instance.forceUpdate();
337329
instance.forceUpdate();
338330
});
339331

340-
it('should warn about `setState` on unmounted components', () => {
332+
it('should not warn about `setState` on unmounted components', () => {
341333
const container = document.createElement('div');
342334
document.body.appendChild(container);
343335

@@ -365,22 +357,10 @@ describe('ReactCompositeComponent', () => {
365357
expect(renders).toBe(1);
366358

367359
instance.setState({value: 1});
368-
369360
expect(renders).toBe(2);
370361

371362
ReactDOM.render(<div />, container);
372-
373-
expect(() => {
374-
instance.setState({value: 2});
375-
}).toErrorDev(
376-
"Warning: Can't perform a React state update on an unmounted " +
377-
'component. This is a no-op, but it indicates a memory leak in your ' +
378-
'application. To fix, cancel all subscriptions and asynchronous ' +
379-
'tasks in the componentWillUnmount method.\n' +
380-
' in Component (at **)\n' +
381-
' in span',
382-
);
383-
363+
instance.setState({value: 2});
384364
expect(renders).toBe(2);
385365
});
386366

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

-103
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import type {Fiber, FiberRoot} from './ReactInternalTypes';
1212
import type {Lanes, Lane} from './ReactFiberLane.new';
1313
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
1414
import type {StackCursor} from './ReactFiberStack.new';
15-
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
1615
import type {Flags} from './ReactFiberFlags';
1716

1817
import {
@@ -53,10 +52,6 @@ import {
5352
scheduleSyncCallback,
5453
scheduleLegacySyncCallback,
5554
} from './ReactFiberSyncTaskQueue.new';
56-
import {
57-
NoFlags as NoHookEffect,
58-
Passive as HookPassive,
59-
} from './ReactHookEffectTags';
6055
import {
6156
logCommitStarted,
6257
logCommitStopped,
@@ -119,7 +114,6 @@ import {LegacyRoot} from './ReactRootTags';
119114
import {
120115
NoFlags,
121116
Placement,
122-
PassiveStatic,
123117
Incomplete,
124118
HostEffectMask,
125119
Hydrating,
@@ -344,10 +338,6 @@ let nestedPassiveUpdateCount: number = 0;
344338
let currentEventTime: number = NoTimestamp;
345339
let currentEventTransitionLane: Lanes = NoLanes;
346340

347-
// Dev only flag that tracks if passive effects are currently being flushed.
348-
// We warn about state updates for unmounted components differently in this case.
349-
let isFlushingPassiveEffects = false;
350-
351341
export function getWorkInProgressRoot(): FiberRoot | null {
352342
return workInProgressRoot;
353343
}
@@ -454,7 +444,6 @@ export function scheduleUpdateOnFiber(
454444

455445
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
456446
if (root === null) {
457-
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
458447
return null;
459448
}
460449

@@ -2048,10 +2037,6 @@ function flushPassiveEffectsImpl() {
20482037
markPassiveEffectsStarted(lanes);
20492038
}
20502039

2051-
if (__DEV__) {
2052-
isFlushingPassiveEffects = true;
2053-
}
2054-
20552040
const prevExecutionContext = executionContext;
20562041
executionContext |= CommitContext;
20572042

@@ -2068,10 +2053,6 @@ function flushPassiveEffectsImpl() {
20682053
}
20692054
}
20702055

2071-
if (__DEV__) {
2072-
isFlushingPassiveEffects = false;
2073-
}
2074-
20752056
if (__DEV__) {
20762057
if (enableDebugTracing) {
20772058
logPassiveEffectsStopped();
@@ -2503,90 +2484,6 @@ function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
25032484
}
25042485
}
25052486

2506-
let didWarnStateUpdateForUnmountedComponent: Set<string> | null = null;
2507-
function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
2508-
if (__DEV__) {
2509-
const tag = fiber.tag;
2510-
if (
2511-
tag !== HostRoot &&
2512-
tag !== ClassComponent &&
2513-
tag !== FunctionComponent &&
2514-
tag !== ForwardRef &&
2515-
tag !== MemoComponent &&
2516-
tag !== SimpleMemoComponent
2517-
) {
2518-
// Only warn for user-defined components, not internal ones like Suspense.
2519-
return;
2520-
}
2521-
2522-
if ((fiber.flags & PassiveStatic) !== NoFlags) {
2523-
const updateQueue: FunctionComponentUpdateQueue | null = (fiber.updateQueue: any);
2524-
if (updateQueue !== null) {
2525-
const lastEffect = updateQueue.lastEffect;
2526-
if (lastEffect !== null) {
2527-
const firstEffect = lastEffect.next;
2528-
2529-
let effect = firstEffect;
2530-
do {
2531-
if (effect.destroy !== undefined) {
2532-
if ((effect.tag & HookPassive) !== NoHookEffect) {
2533-
return;
2534-
}
2535-
}
2536-
effect = effect.next;
2537-
} while (effect !== firstEffect);
2538-
}
2539-
}
2540-
}
2541-
// We show the whole stack but dedupe on the top component's name because
2542-
// the problematic code almost always lies inside that component.
2543-
const componentName = getComponentNameFromFiber(fiber) || 'ReactComponent';
2544-
if (didWarnStateUpdateForUnmountedComponent !== null) {
2545-
if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
2546-
return;
2547-
}
2548-
didWarnStateUpdateForUnmountedComponent.add(componentName);
2549-
} else {
2550-
didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
2551-
}
2552-
2553-
if (isFlushingPassiveEffects) {
2554-
// Do not warn if we are currently flushing passive effects!
2555-
//
2556-
// React can't directly detect a memory leak, but there are some clues that warn about one.
2557-
// One of these clues is when an unmounted React component tries to update its state.
2558-
// For example, if a component forgets to remove an event listener when unmounting,
2559-
// that listener may be called later and try to update state,
2560-
// at which point React would warn about the potential leak.
2561-
//
2562-
// Warning signals are the most useful when they're strong.
2563-
// (So we should avoid false positive warnings.)
2564-
// Updating state from within an effect cleanup function is sometimes a necessary pattern, e.g.:
2565-
// 1. Updating an ancestor that a component had registered itself with on mount.
2566-
// 2. Resetting state when a component is hidden after going offscreen.
2567-
} else {
2568-
const previousFiber = ReactCurrentFiberCurrent;
2569-
try {
2570-
setCurrentDebugFiberInDEV(fiber);
2571-
console.error(
2572-
"Can't perform a React state update on an unmounted component. This " +
2573-
'is a no-op, but it indicates a memory leak in your application. To ' +
2574-
'fix, cancel all subscriptions and asynchronous tasks in %s.',
2575-
tag === ClassComponent
2576-
? 'the componentWillUnmount method'
2577-
: 'a useEffect cleanup function',
2578-
);
2579-
} finally {
2580-
if (previousFiber) {
2581-
setCurrentDebugFiberInDEV(fiber);
2582-
} else {
2583-
resetCurrentDebugFiberInDEV();
2584-
}
2585-
}
2586-
}
2587-
}
2588-
}
2589-
25902487
let beginWork;
25912488
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
25922489
const dummyFiber = null;

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

-103
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import type {Fiber, FiberRoot} from './ReactInternalTypes';
1212
import type {Lanes, Lane} from './ReactFiberLane.old';
1313
import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
1414
import type {StackCursor} from './ReactFiberStack.old';
15-
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.old';
1615
import type {Flags} from './ReactFiberFlags';
1716

1817
import {
@@ -53,10 +52,6 @@ import {
5352
scheduleSyncCallback,
5453
scheduleLegacySyncCallback,
5554
} from './ReactFiberSyncTaskQueue.old';
56-
import {
57-
NoFlags as NoHookEffect,
58-
Passive as HookPassive,
59-
} from './ReactHookEffectTags';
6055
import {
6156
logCommitStarted,
6257
logCommitStopped,
@@ -119,7 +114,6 @@ import {LegacyRoot} from './ReactRootTags';
119114
import {
120115
NoFlags,
121116
Placement,
122-
PassiveStatic,
123117
Incomplete,
124118
HostEffectMask,
125119
Hydrating,
@@ -344,10 +338,6 @@ let nestedPassiveUpdateCount: number = 0;
344338
let currentEventTime: number = NoTimestamp;
345339
let currentEventTransitionLane: Lanes = NoLanes;
346340

347-
// Dev only flag that tracks if passive effects are currently being flushed.
348-
// We warn about state updates for unmounted components differently in this case.
349-
let isFlushingPassiveEffects = false;
350-
351341
export function getWorkInProgressRoot(): FiberRoot | null {
352342
return workInProgressRoot;
353343
}
@@ -454,7 +444,6 @@ export function scheduleUpdateOnFiber(
454444

455445
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
456446
if (root === null) {
457-
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
458447
return null;
459448
}
460449

@@ -2048,10 +2037,6 @@ function flushPassiveEffectsImpl() {
20482037
markPassiveEffectsStarted(lanes);
20492038
}
20502039

2051-
if (__DEV__) {
2052-
isFlushingPassiveEffects = true;
2053-
}
2054-
20552040
const prevExecutionContext = executionContext;
20562041
executionContext |= CommitContext;
20572042

@@ -2068,10 +2053,6 @@ function flushPassiveEffectsImpl() {
20682053
}
20692054
}
20702055

2071-
if (__DEV__) {
2072-
isFlushingPassiveEffects = false;
2073-
}
2074-
20752056
if (__DEV__) {
20762057
if (enableDebugTracing) {
20772058
logPassiveEffectsStopped();
@@ -2503,90 +2484,6 @@ function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
25032484
}
25042485
}
25052486

2506-
let didWarnStateUpdateForUnmountedComponent: Set<string> | null = null;
2507-
function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
2508-
if (__DEV__) {
2509-
const tag = fiber.tag;
2510-
if (
2511-
tag !== HostRoot &&
2512-
tag !== ClassComponent &&
2513-
tag !== FunctionComponent &&
2514-
tag !== ForwardRef &&
2515-
tag !== MemoComponent &&
2516-
tag !== SimpleMemoComponent
2517-
) {
2518-
// Only warn for user-defined components, not internal ones like Suspense.
2519-
return;
2520-
}
2521-
2522-
if ((fiber.flags & PassiveStatic) !== NoFlags) {
2523-
const updateQueue: FunctionComponentUpdateQueue | null = (fiber.updateQueue: any);
2524-
if (updateQueue !== null) {
2525-
const lastEffect = updateQueue.lastEffect;
2526-
if (lastEffect !== null) {
2527-
const firstEffect = lastEffect.next;
2528-
2529-
let effect = firstEffect;
2530-
do {
2531-
if (effect.destroy !== undefined) {
2532-
if ((effect.tag & HookPassive) !== NoHookEffect) {
2533-
return;
2534-
}
2535-
}
2536-
effect = effect.next;
2537-
} while (effect !== firstEffect);
2538-
}
2539-
}
2540-
}
2541-
// We show the whole stack but dedupe on the top component's name because
2542-
// the problematic code almost always lies inside that component.
2543-
const componentName = getComponentNameFromFiber(fiber) || 'ReactComponent';
2544-
if (didWarnStateUpdateForUnmountedComponent !== null) {
2545-
if (didWarnStateUpdateForUnmountedComponent.has(componentName)) {
2546-
return;
2547-
}
2548-
didWarnStateUpdateForUnmountedComponent.add(componentName);
2549-
} else {
2550-
didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
2551-
}
2552-
2553-
if (isFlushingPassiveEffects) {
2554-
// Do not warn if we are currently flushing passive effects!
2555-
//
2556-
// React can't directly detect a memory leak, but there are some clues that warn about one.
2557-
// One of these clues is when an unmounted React component tries to update its state.
2558-
// For example, if a component forgets to remove an event listener when unmounting,
2559-
// that listener may be called later and try to update state,
2560-
// at which point React would warn about the potential leak.
2561-
//
2562-
// Warning signals are the most useful when they're strong.
2563-
// (So we should avoid false positive warnings.)
2564-
// Updating state from within an effect cleanup function is sometimes a necessary pattern, e.g.:
2565-
// 1. Updating an ancestor that a component had registered itself with on mount.
2566-
// 2. Resetting state when a component is hidden after going offscreen.
2567-
} else {
2568-
const previousFiber = ReactCurrentFiberCurrent;
2569-
try {
2570-
setCurrentDebugFiberInDEV(fiber);
2571-
console.error(
2572-
"Can't perform a React state update on an unmounted component. This " +
2573-
'is a no-op, but it indicates a memory leak in your application. To ' +
2574-
'fix, cancel all subscriptions and asynchronous tasks in %s.',
2575-
tag === ClassComponent
2576-
? 'the componentWillUnmount method'
2577-
: 'a useEffect cleanup function',
2578-
);
2579-
} finally {
2580-
if (previousFiber) {
2581-
setCurrentDebugFiberInDEV(fiber);
2582-
} else {
2583-
resetCurrentDebugFiberInDEV();
2584-
}
2585-
}
2586-
}
2587-
}
2588-
}
2589-
25902487
let beginWork;
25912488
if (__DEV__ && replayFailedUnitOfWorkWithInvokeGuardedCallback) {
25922489
const dummyFiber = null;

0 commit comments

Comments
 (0)