Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5ee0efe

Browse files
author
Brian Vaughn
authored
Remove state update warning for passive effect cleanup functions (facebook#18453)
1 parent d8d2b6e commit 5ee0efe

File tree

3 files changed

+56
-45
lines changed

3 files changed

+56
-45
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 9 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ import {
160160
import getComponentName from 'shared/getComponentName';
161161
import ReactStrictModeWarnings from './ReactStrictModeWarnings';
162162
import {
163-
current as currentDebugFiberInDEV,
164163
isRendering as ReactCurrentDebugFiberIsRenderingInDEV,
165164
resetCurrentFiber as resetCurrentDebugFiberInDEV,
166165
setCurrentFiber as setCurrentDebugFiberInDEV,
@@ -2736,38 +2735,20 @@ function warnAboutUpdateOnUnmountedFiberInDEV(fiber) {
27362735
didWarnStateUpdateForUnmountedComponent = new Set([componentName]);
27372736
}
27382737

2739-
// If we are currently flushing passive effects, change the warning text.
27402738
if (isFlushingPassiveEffects) {
2739+
// Do not warn if we are currently flushing passive effects!
2740+
//
27412741
// React can't directly detect a memory leak, but there are some clues that warn about one.
27422742
// One of these clues is when an unmounted React component tries to update its state.
27432743
// For example, if a component forgets to remove an event listener when unmounting,
2744-
// that listener may be called later and try to update state, at which point React would warn about the potential leak.
2745-
//
2746-
// Warning signals like this are more useful if they're strong.
2747-
// For this reason, it's good to always avoid updating state from inside of an effect's cleanup function.
2748-
// Even when you know there is no potential leak, React has no way to know and so it will warn anyway.
2749-
// In most cases we suggest moving state updates to the useEffect() body instead.
2750-
// This works so long as the component is updating its own state (or the state of a descendant).
2744+
// that listener may be called later and try to update state,
2745+
// at which point React would warn about the potential leak.
27512746
//
2752-
// However this will not work when a component updates its parent state in a cleanup function.
2753-
// If such a component is unmounted but its parent remains mounted, the state will be out of sync.
2754-
// For this reason, we avoid showing the warning if a component is updating an ancestor.
2755-
let currentTargetFiber = fiber;
2756-
while (currentTargetFiber !== null) {
2757-
// currentDebugFiberInDEV owns the effect destroy function currently being invoked.
2758-
if (
2759-
currentDebugFiberInDEV === currentTargetFiber ||
2760-
currentDebugFiberInDEV === currentTargetFiber.alternate
2761-
) {
2762-
console.error(
2763-
"Can't perform a React state update from within a useEffect cleanup function. " +
2764-
'To fix, move state updates to the useEffect() body.%s',
2765-
getStackByFiberInDevAndProd(((currentDebugFiberInDEV: any): Fiber)),
2766-
);
2767-
break;
2768-
}
2769-
currentTargetFiber = currentTargetFiber.return;
2770-
}
2747+
// Warning signals are the most useful when they're strong.
2748+
// (So we should avoid false positive warnings.)
2749+
// Updating state from within an effect cleanup function is sometimes a necessary pattern, e.g.:
2750+
// 1. Updating an ancestor that a component had registered itself with on mount.
2751+
// 2. Resetting state when a component is hidden after going offscreen.
27712752
} else {
27722753
console.error(
27732754
"Can't perform a React state update on an unmounted component. This " +

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

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,7 +1307,49 @@ describe('ReactHooksWithNoopRenderer', () => {
13071307
});
13081308
});
13091309

1310-
it('shows a warning when a component updates its own state from within passive unmount function', () => {
1310+
it('still warns if there are updates after pending passive unmount effects have been flushed', () => {
1311+
let updaterFunction;
1312+
1313+
function Component() {
1314+
Scheduler.unstable_yieldValue('Component');
1315+
const [state, setState] = React.useState(false);
1316+
updaterFunction = setState;
1317+
React.useEffect(() => {
1318+
Scheduler.unstable_yieldValue('passive create');
1319+
return () => {
1320+
Scheduler.unstable_yieldValue('passive destroy');
1321+
};
1322+
}, []);
1323+
return state;
1324+
}
1325+
1326+
act(() => {
1327+
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
1328+
Scheduler.unstable_yieldValue('Sync effect'),
1329+
);
1330+
});
1331+
expect(Scheduler).toHaveYielded([
1332+
'Component',
1333+
'Sync effect',
1334+
'passive create',
1335+
]);
1336+
1337+
ReactNoop.unmountRootWithID('root');
1338+
expect(Scheduler).toFlushAndYield(['passive destroy']);
1339+
1340+
act(() => {
1341+
expect(() => {
1342+
updaterFunction(true);
1343+
}).toErrorDev(
1344+
"Warning: Can't perform a React state update on an unmounted component. " +
1345+
'This is a no-op, but it indicates a memory leak in your application. ' +
1346+
'To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.\n' +
1347+
' in Component (at **)',
1348+
);
1349+
});
1350+
});
1351+
1352+
it('does not show a warning when a component updates its own state from within passive unmount function', () => {
13111353
function Component() {
13121354
Scheduler.unstable_yieldValue('Component');
13131355
const [didLoad, setDidLoad] = React.useState(false);
@@ -1333,17 +1375,11 @@ describe('ReactHooksWithNoopRenderer', () => {
13331375

13341376
// Unmount but don't process pending passive destroy function
13351377
ReactNoop.unmountRootWithID('root');
1336-
expect(() => {
1337-
expect(Scheduler).toFlushAndYield(['passive destroy']);
1338-
}).toErrorDev(
1339-
"Warning: Can't perform a React state update from within a useEffect cleanup function. " +
1340-
'To fix, move state updates to the useEffect() body.\n' +
1341-
' in Component (at **)',
1342-
);
1378+
expect(Scheduler).toFlushAndYield(['passive destroy']);
13431379
});
13441380
});
13451381

1346-
it('shows a warning when a component updates a childs state from within passive unmount function', () => {
1382+
it('does not show a warning when a component updates a childs state from within passive unmount function', () => {
13471383
function Parent() {
13481384
Scheduler.unstable_yieldValue('Parent');
13491385
const updaterRef = React.useRef(null);
@@ -1378,13 +1414,7 @@ describe('ReactHooksWithNoopRenderer', () => {
13781414

13791415
// Unmount but don't process pending passive destroy function
13801416
ReactNoop.unmountRootWithID('root');
1381-
expect(() => {
1382-
expect(Scheduler).toFlushAndYield(['Parent passive destroy']);
1383-
}).toErrorDev(
1384-
"Warning: Can't perform a React state update from within a useEffect cleanup function. " +
1385-
'To fix, move state updates to the useEffect() body.\n' +
1386-
' in Parent (at **)',
1387-
);
1417+
expect(Scheduler).toFlushAndYield(['Parent passive destroy']);
13881418
});
13891419
});
13901420

scripts/jest/matchers/toWarnDev.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const jestDiff = require('jest-diff').default;
3+
const jestDiff = require('jest-diff');
44
const util = require('util');
55
const shouldIgnoreConsoleError = require('../shouldIgnoreConsoleError');
66

0 commit comments

Comments
 (0)