mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Don't double-invoke effects in legacy roots (#20028)
Large legacy applications are likely to be difficult to update to handle this feature, and it wouldn't add any value– since newer APIs that require this resilience are not legacy compatible.
This commit is contained in:
@@ -30,7 +30,13 @@ import invariant from 'shared/invariant';
|
||||
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
|
||||
|
||||
import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
|
||||
import {DebugTracingMode, StrictMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
DebugTracingMode,
|
||||
NoMode,
|
||||
StrictMode,
|
||||
} from './ReactTypeOfMode';
|
||||
|
||||
import {
|
||||
enqueueUpdate,
|
||||
@@ -891,7 +897,12 @@ function mountClassInstance(
|
||||
}
|
||||
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
// Never double-invoke effects for legacy roots.
|
||||
workInProgress.flags |= MountLayoutDev | Update;
|
||||
} else {
|
||||
workInProgress.flags |= Update;
|
||||
@@ -965,7 +976,11 @@ function resumeMountClassInstance(
|
||||
// If an update was already in progress, we should schedule an Update
|
||||
// effect even though we're bailing out, so that cWU/cDU are called.
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
workInProgress.flags |= MountLayoutDev | Update;
|
||||
} else {
|
||||
workInProgress.flags |= Update;
|
||||
@@ -1012,7 +1027,11 @@ function resumeMountClassInstance(
|
||||
}
|
||||
}
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
workInProgress.flags |= MountLayoutDev | Update;
|
||||
} else {
|
||||
workInProgress.flags |= Update;
|
||||
@@ -1022,7 +1041,11 @@ function resumeMountClassInstance(
|
||||
// If an update was already in progress, we should schedule an Update
|
||||
// effect even though we're bailing out, so that cWU/cDU are called.
|
||||
if (typeof instance.componentDidMount === 'function') {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
workInProgress.flags |= MountLayoutDev | Update;
|
||||
} else {
|
||||
workInProgress.flags |= Update;
|
||||
|
||||
@@ -2024,6 +2024,8 @@ function commitPassiveMount(
|
||||
|
||||
function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
// We don't need to re-check for legacy roots here.
|
||||
// This function will not be called within legacy roots.
|
||||
switch (fiber.tag) {
|
||||
case FunctionComponent:
|
||||
case ForwardRef:
|
||||
@@ -2057,6 +2059,8 @@ function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
|
||||
|
||||
function invokePassiveEffectMountInDEV(fiber: Fiber): void {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
// We don't need to re-check for legacy roots here.
|
||||
// This function will not be called within legacy roots.
|
||||
switch (fiber.tag) {
|
||||
case FunctionComponent:
|
||||
case ForwardRef:
|
||||
@@ -2081,6 +2085,8 @@ function invokePassiveEffectMountInDEV(fiber: Fiber): void {
|
||||
|
||||
function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
// We don't need to re-check for legacy roots here.
|
||||
// This function will not be called within legacy roots.
|
||||
switch (fiber.tag) {
|
||||
case FunctionComponent:
|
||||
case ForwardRef:
|
||||
@@ -2113,6 +2119,8 @@ function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
|
||||
|
||||
function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
// We don't need to re-check for legacy roots here.
|
||||
// This function will not be called within legacy roots.
|
||||
switch (fiber.tag) {
|
||||
case FunctionComponent:
|
||||
case ForwardRef:
|
||||
|
||||
@@ -29,7 +29,12 @@ import {
|
||||
enableDoubleInvokingEffects,
|
||||
} from 'shared/ReactFeatureFlags';
|
||||
|
||||
import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
|
||||
import {
|
||||
NoMode,
|
||||
BlockingMode,
|
||||
ConcurrentMode,
|
||||
DebugTracingMode,
|
||||
} from './ReactTypeOfMode';
|
||||
import {
|
||||
NoLane,
|
||||
NoLanes,
|
||||
@@ -485,7 +490,11 @@ export function bailoutHooks(
|
||||
lanes: Lanes,
|
||||
) {
|
||||
workInProgress.updateQueue = current.updateQueue;
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
workInProgress.flags &= ~(
|
||||
MountPassiveDevEffect |
|
||||
PassiveEffect |
|
||||
@@ -1253,7 +1262,11 @@ function mountEffect(
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
return mountEffectImpl(
|
||||
MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
|
||||
HookPassive,
|
||||
@@ -1287,7 +1300,11 @@ function mountLayoutEffect(
|
||||
create: () => (() => void) | void,
|
||||
deps: Array<mixed> | void | null,
|
||||
): void {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
return mountEffectImpl(
|
||||
MountLayoutDevEffect | UpdateEffect,
|
||||
HookLayout,
|
||||
@@ -1355,7 +1372,11 @@ function mountImperativeHandle<T>(
|
||||
const effectDeps =
|
||||
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
|
||||
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
enableDoubleInvokingEffects &&
|
||||
(currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
|
||||
) {
|
||||
return mountEffectImpl(
|
||||
MountLayoutDevEffect | UpdateEffect,
|
||||
HookLayout,
|
||||
|
||||
@@ -2874,6 +2874,11 @@ function commitDoubleInvokeEffectsInDEV(
|
||||
hasPassiveEffects: boolean,
|
||||
) {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
// Never double-invoke effects for legacy roots.
|
||||
if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentDebugFiberInDEV(fiber);
|
||||
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
|
||||
if (hasPassiveEffects) {
|
||||
@@ -2898,6 +2903,8 @@ function invokeEffectsInDev(
|
||||
invokeEffectFn: (fiber: Fiber) => void,
|
||||
): void {
|
||||
if (__DEV__ && enableDoubleInvokingEffects) {
|
||||
// We don't need to re-check for legacy roots here.
|
||||
// This function will not be called within legacy roots.
|
||||
let fiber = firstChild;
|
||||
while (fiber !== null) {
|
||||
if (fiber.child !== null) {
|
||||
|
||||
@@ -11,17 +11,44 @@
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactNoop;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
let act;
|
||||
|
||||
describe('ReactDoubleInvokeEvents', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
ReactFeatureFlags.enableDoubleInvokingEffects = __VARIANT__;
|
||||
act = ReactTestRenderer.unstable_concurrentAct;
|
||||
});
|
||||
|
||||
it('should not double invoke effects in legacy mode', () => {
|
||||
function App({text}) {
|
||||
React.useEffect(() => {
|
||||
Scheduler.unstable_yieldValue('useEffect mount');
|
||||
return () => Scheduler.unstable_yieldValue('useEffect unmount');
|
||||
});
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
Scheduler.unstable_yieldValue('useLayoutEffect mount');
|
||||
return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
|
||||
});
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactTestRenderer.create(<App text={'mount'} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'useLayoutEffect mount',
|
||||
'useEffect mount',
|
||||
]);
|
||||
});
|
||||
|
||||
it('double invoking for effects works properly', () => {
|
||||
@@ -38,8 +65,12 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
|
||||
return text;
|
||||
}
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'mount'} />);
|
||||
|
||||
let renderer;
|
||||
act(() => {
|
||||
renderer = ReactTestRenderer.create(<App text={'mount'} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
@@ -58,8 +89,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
]);
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'update'} />);
|
||||
act(() => {
|
||||
renderer.update(<App text={'update'} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
@@ -69,8 +100,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
'useEffect mount',
|
||||
]);
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(null);
|
||||
act(() => {
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
@@ -94,8 +125,11 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
return text;
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'mount'} />);
|
||||
let renderer;
|
||||
act(() => {
|
||||
renderer = ReactTestRenderer.create(<App text={'mount'} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
@@ -114,8 +148,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
]);
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'update'} />);
|
||||
act(() => {
|
||||
renderer.update(<App text={'update'} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
@@ -125,8 +159,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
'useEffect Two mount',
|
||||
]);
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(null);
|
||||
act(() => {
|
||||
renderer.unmount(null);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
@@ -152,8 +186,11 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
return text;
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'mount'} />);
|
||||
let renderer;
|
||||
act(() => {
|
||||
renderer = ReactTestRenderer.create(<App text={'mount'} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
@@ -172,8 +209,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
]);
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'update'} />);
|
||||
act(() => {
|
||||
renderer.update(<App text={'update'} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
@@ -183,8 +220,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
'useLayoutEffect Two mount',
|
||||
]);
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(null);
|
||||
act(() => {
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
@@ -206,8 +243,11 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
return text;
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'mount'} />);
|
||||
let renderer;
|
||||
act(() => {
|
||||
renderer = ReactTestRenderer.create(<App text={'mount'} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
@@ -224,8 +264,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
]);
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'update'} />);
|
||||
act(() => {
|
||||
renderer.update(<App text={'update'} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
@@ -233,8 +273,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
'useEffect mount',
|
||||
]);
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(null);
|
||||
act(() => {
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
@@ -264,8 +304,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App />);
|
||||
act(() => {
|
||||
ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
@@ -298,8 +338,11 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'mount'} />);
|
||||
let renderer;
|
||||
act(() => {
|
||||
renderer = ReactTestRenderer.create(<App text={'mount'} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
@@ -312,19 +355,45 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
expect(Scheduler).toHaveYielded(['componentDidMount']);
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'update'} />);
|
||||
act(() => {
|
||||
renderer.update(<App text={'update'} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['componentDidUpdate']);
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(null);
|
||||
act(() => {
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['componentWillUnmount']);
|
||||
});
|
||||
|
||||
it('should not double invoke class lifecycles in legacy mode', () => {
|
||||
class App extends React.PureComponent {
|
||||
componentDidMount() {
|
||||
Scheduler.unstable_yieldValue('componentDidMount');
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
Scheduler.unstable_yieldValue('componentDidUpdate');
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
Scheduler.unstable_yieldValue('componentWillUnmount');
|
||||
}
|
||||
|
||||
render() {
|
||||
return this.props.text;
|
||||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
ReactTestRenderer.create(<App text={'mount'} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['componentDidMount']);
|
||||
});
|
||||
|
||||
it('double flushing passive effects only results in one double invoke', () => {
|
||||
function App({text}) {
|
||||
const [state, setState] = React.useState(0);
|
||||
@@ -345,8 +414,10 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
return text;
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'mount'} />);
|
||||
act(() => {
|
||||
ReactTestRenderer.create(<App text={'mount'} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
@@ -410,8 +481,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
return showChild && <Child />;
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App />);
|
||||
act(() => {
|
||||
ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
@@ -430,7 +501,7 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
]);
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
act(() => {
|
||||
_setShowChild(true);
|
||||
});
|
||||
|
||||
@@ -495,8 +566,11 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'mount'} />);
|
||||
let renderer;
|
||||
act(() => {
|
||||
renderer = ReactTestRenderer.create(<App text={'mount'} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
|
||||
if (__DEV__ && __VARIANT__) {
|
||||
@@ -519,8 +593,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
]);
|
||||
}
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(<App text={'mount'} />);
|
||||
act(() => {
|
||||
renderer.update(<App text={'mount'} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
@@ -530,8 +604,8 @@ describe('ReactDoubleInvokeEvents', () => {
|
||||
'useEffect mount',
|
||||
]);
|
||||
|
||||
ReactNoop.act(() => {
|
||||
ReactNoop.render(null);
|
||||
act(() => {
|
||||
renderer.unmount();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
|
||||
@@ -4180,7 +4180,11 @@ describe('Profiler', () => {
|
||||
const interactions = SchedulerTracing.unstable_getCurrent();
|
||||
expect(interactions.size).toBe(1);
|
||||
interaction = Array.from(interactions)[0];
|
||||
ReactTestRenderer.create(<Component />);
|
||||
ReactTestRendererAct(() => {
|
||||
ReactTestRenderer.create(<Component />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
Scheduler.unstable_flushAll();
|
||||
|
||||
Reference in New Issue
Block a user