Double Invoke Effects in __DEV__ (in old reconciler fork) (#20415)
We originally added a new DEV behavior of double-invoking effects during mount to our new reconciler fork in PRs #19523 and #19935 and later refined it to only affect modern roots in PR #20028. This PR adds that behavior to the old reconciler fork with a small twist– the behavior applies to StrictMode subtrees, regardless of the root type. This commit also adds a few additional tests that weren't in the original commits.
Brian Vaughn committed
Dec 9, 2020 at 14:18 UTC
56a632adb657fe5b704125caabf29f0cc03b0b12
7 files changed
+1099
-50
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+39
-6
@@ -12,13 +12,14 @@ import type {Lanes} from './ReactFiberLane.old';
12
import type {UpdateQueue} from './ReactUpdateQueue.old';
13
14
import * as React from 'react';
15
-import {Update, Snapshot} from './ReactFiberFlags';
15
+import {Update, Snapshot, MountLayoutDev} from './ReactFiberFlags';
16
import {
17
debugRenderPhaseSideEffectsForStrictMode,
18
disableLegacyContext,
19
enableDebugTracing,
20
enableSchedulingProfiler,
21
warnAboutDeprecatedLifecycles,
22
+ enableDoubleInvokingEffects,
23
} from 'shared/ReactFeatureFlags';
24
import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
25
import {isMounted} from './ReactFiberTreeReflection';
@@ -29,7 +30,7 @@ import invariant from 'shared/invariant';
30
import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
31
32
import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
32
-import {DebugTracingMode, StrictMode} from './ReactTypeOfMode';
33
+import {DebugTracingMode, NoMode, StrictMode} from './ReactTypeOfMode';
34
35
import {
36
enqueueUpdate,
@@ -890,7 +891,15 @@ function mountClassInstance(
891
}
892
893
if (typeof instance.componentDidMount === 'function') {
893
- workInProgress.flags |= Update;
894
+ if (
895
+ __DEV__ &&
896
+ enableDoubleInvokingEffects &&
897
+ (workInProgress.mode & StrictMode) !== NoMode
898
+ ) {
899
+ workInProgress.flags |= MountLayoutDev | Update;
900
+ } else {
901
+ workInProgress.flags |= Update;
902
+ }
903
}
904
}
905
@@ -960,7 +969,15 @@ function resumeMountClassInstance(
969
// If an update was already in progress, we should schedule an Update
970
// effect even though we're bailing out, so that cWU/cDU are called.
971
if (typeof instance.componentDidMount === 'function') {
963
- workInProgress.flags |= Update;
972
+ if (
973
+ __DEV__ &&
974
+ enableDoubleInvokingEffects &&
975
+ (workInProgress.mode & StrictMode) !== NoMode
976
+ ) {
977
+ workInProgress.flags |= MountLayoutDev | Update;
978
+ } else {
979
+ workInProgress.flags |= Update;
980
+ }
981
}
982
return false;
983
}
@@ -1003,13 +1020,29 @@ function resumeMountClassInstance(
1020
}
1021
}
1022
if (typeof instance.componentDidMount === 'function') {
1006
- workInProgress.flags |= Update;
1023
+ if (
1024
+ __DEV__ &&
1025
+ enableDoubleInvokingEffects &&
1026
+ (workInProgress.mode & StrictMode) !== NoMode
1027
+ ) {
1028
+ workInProgress.flags |= MountLayoutDev | Update;
1029
+ } else {
1030
+ workInProgress.flags |= Update;
1031
+ }
1032
}
1033
} else {
1034
// If an update was already in progress, we should schedule an Update
1035
// effect even though we're bailing out, so that cWU/cDU are called.
1036
if (typeof instance.componentDidMount === 'function') {
1012
- workInProgress.flags |= Update;
1037
+ if (
1038
+ __DEV__ &&
1039
+ enableDoubleInvokingEffects &&
1040
+ (workInProgress.mode & StrictMode) !== NoMode
1041
+ ) {
1042
+ workInProgress.flags |= MountLayoutDev | Update;
1043
+ } else {
1044
+ workInProgress.flags |= Update;
1045
+ }
1046
}
1047
1048
// If shouldComponentUpdate returned false, we should still update the
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+115
@@ -35,6 +35,7 @@ import {
35
enableFundamentalAPI,
36
enableSuspenseCallback,
37
enableScopeAPI,
38
+ enableDoubleInvokingEffects,
39
} from 'shared/ReactFeatureFlags';
40
import {
41
FunctionComponent,
@@ -1798,6 +1799,116 @@ function commitResetTextContent(current: Fiber) {
1799
resetTextContent(current.stateNode);
1800
}
1801
1802
+function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
1803
+ if (__DEV__ && enableDoubleInvokingEffects) {
1804
+ switch (fiber.tag) {
1805
+ case FunctionComponent:
1806
+ case ForwardRef:
1807
+ case SimpleMemoComponent: {
1808
+ invokeGuardedCallback(
1809
+ null,
1810
+ commitHookEffectListMount,
1811
+ null,
1812
+ HookLayout | HookHasEffect,
1813
+ fiber,
1814
+ );
1815
+ if (hasCaughtError()) {
1816
+ const mountError = clearCaughtError();
1817
+ captureCommitPhaseError(fiber, mountError);
1818
+ }
1819
+ break;
1820
+ }
1821
+ case ClassComponent: {
1822
+ const instance = fiber.stateNode;
1823
+ invokeGuardedCallback(null, instance.componentDidMount, instance);
1824
+ if (hasCaughtError()) {
1825
+ const mountError = clearCaughtError();
1826
+ captureCommitPhaseError(fiber, mountError);
1827
+ }
1828
+ break;
1829
+ }
1830
+ }
1831
+ }
1832
+}
1833
+
1834
+function invokePassiveEffectMountInDEV(fiber: Fiber): void {
1835
+ if (__DEV__ && enableDoubleInvokingEffects) {
1836
+ switch (fiber.tag) {
1837
+ case FunctionComponent:
1838
+ case ForwardRef:
1839
+ case SimpleMemoComponent: {
1840
+ invokeGuardedCallback(
1841
+ null,
1842
+ commitHookEffectListMount,
1843
+ null,
1844
+ HookPassive | HookHasEffect,
1845
+ fiber,
1846
+ );
1847
+ if (hasCaughtError()) {
1848
+ const mountError = clearCaughtError();
1849
+ captureCommitPhaseError(fiber, mountError);
1850
+ }
1851
+ break;
1852
+ }
1853
+ }
1854
+ }
1855
+}
1856
+
1857
+function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
1858
+ if (__DEV__ && enableDoubleInvokingEffects) {
1859
+ switch (fiber.tag) {
1860
+ case FunctionComponent:
1861
+ case ForwardRef:
1862
+ case SimpleMemoComponent: {
1863
+ invokeGuardedCallback(
1864
+ null,
1865
+ commitHookEffectListUnmount,
1866
+ null,
1867
+ HookLayout | HookHasEffect,
1868
+ fiber,
1869
+ fiber.return,
1870
+ );
1871
+ if (hasCaughtError()) {
1872
+ const unmountError = clearCaughtError();
1873
+ captureCommitPhaseError(fiber, unmountError);
1874
+ }
1875
+ break;
1876
+ }
1877
+ case ClassComponent: {
1878
+ const instance = fiber.stateNode;
1879
+ if (typeof instance.componentWillUnmount === 'function') {
1880
+ safelyCallComponentWillUnmount(fiber, instance);
1881
+ }
1882
+ break;
1883
+ }
1884
+ }
1885
+ }
1886
+}
1887
+
1888
+function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
1889
+ if (__DEV__ && enableDoubleInvokingEffects) {
1890
+ switch (fiber.tag) {
1891
+ case FunctionComponent:
1892
+ case ForwardRef:
1893
+ case SimpleMemoComponent: {
1894
+ invokeGuardedCallback(
1895
+ null,
1896
+ commitHookEffectListUnmount,
1897
+ null,
1898
+ HookPassive | HookHasEffect,
1899
+ fiber,
1900
+ fiber.return,
1901
+ );
1902
+ if (hasCaughtError()) {
1903
+ const unmountError = clearCaughtError();
1904
+ captureCommitPhaseError(fiber, unmountError);
1905
+ }
1906
+ break;
1907
+ }
1908
+ }
1909
+ }
1910
+}
1911
+
1912
export {
1913
commitBeforeMutationLifeCycles,
1914
commitResetTextContent,
@@ -1807,4 +1918,8 @@ export {
1918
commitLifeCycles,
1919
commitAttachRef,
1920
commitDetachRef,
1921
+ invokeLayoutEffectMountInDEV,
1922
+ invokeLayoutEffectUnmountInDEV,
1923
+ invokePassiveEffectMountInDEV,
1924
+ invokePassiveEffectUnmountInDEV,
1925
};
packages/react-reconciler/src/ReactFiberHooks.old.js
+87
-16
@@ -28,9 +28,15 @@ import {
28
enableCache,
29
decoupleUpdatePriorityFromScheduler,
30
enableUseRefAccessWarning,
31
+ enableDoubleInvokingEffects,
32
} from 'shared/ReactFeatureFlags';
33
33
-import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
34
+import {
35
+ NoMode,
36
+ BlockingMode,
37
+ DebugTracingMode,
38
+ StrictMode,
39
+} from './ReactTypeOfMode';
40
import {
41
NoLane,
42
NoLanes,
@@ -49,6 +55,8 @@ import {readContext} from './ReactFiberNewContext.old';
55
import {
56
Update as UpdateEffect,
57
Passive as PassiveEffect,
58
+ MountLayoutDev as MountLayoutDevEffect,
59
+ MountPassiveDev as MountPassiveDevEffect,
60
} from './ReactFiberFlags';
61
import {
62
HasEffect as HookHasEffect,
@@ -467,7 +475,20 @@ export function bailoutHooks(
475
lanes: Lanes,
476
) {
477
workInProgress.updateQueue = current.updateQueue;
470
- workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
478
+ if (
479
+ __DEV__ &&
480
+ enableDoubleInvokingEffects &&
481
+ (workInProgress.mode & StrictMode) !== NoMode
482
+ ) {
483
+ workInProgress.flags &= ~(
484
+ MountLayoutDevEffect |
485
+ MountPassiveDevEffect |
486
+ PassiveEffect |
487
+ UpdateEffect
488
+ );
489
+ } else {
490
+ workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
491
+ }
492
current.lanes = removeLanes(current.lanes, lanes);
493
}
494
@@ -1303,12 +1324,26 @@ function mountEffect(
1324
warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
1325
}
1326
}
1306
- return mountEffectImpl(
1307
- UpdateEffect | PassiveEffect,
1308
- HookPassive,
1309
- create,
1310
- deps,
1311
- );
1327
+
1328
+ if (
1329
+ __DEV__ &&
1330
+ enableDoubleInvokingEffects &&
1331
+ (currentlyRenderingFiber.mode & StrictMode) !== NoMode
1332
+ ) {
1333
+ return mountEffectImpl(
1334
+ UpdateEffect | PassiveEffect | MountPassiveDevEffect,
1335
+ HookPassive,
1336
+ create,
1337
+ deps,
1338
+ );
1339
+ } else {
1340
+ return mountEffectImpl(
1341
+ UpdateEffect | PassiveEffect,
1342
+ HookPassive,
1343
+ create,
1344
+ deps,
1345
+ );
1346
+ }
1347
}
1348
1349
function updateEffect(
@@ -1333,7 +1368,20 @@ function mountLayoutEffect(
1368
create: () => (() => void) | void,
1369
deps: Array<mixed> | void | null,
1370
): void {
1336
- return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1371
+ if (
1372
+ __DEV__ &&
1373
+ enableDoubleInvokingEffects &&
1374
+ (currentlyRenderingFiber.mode & StrictMode) !== NoMode
1375
+ ) {
1376
+ return mountEffectImpl(
1377
+ UpdateEffect | MountLayoutDevEffect,
1378
+ HookLayout,
1379
+ create,
1380
+ deps,
1381
+ );
1382
+ } else {
1383
+ return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1384
+ }
1385
}
1386
1387
function updateLayoutEffect(
@@ -1392,12 +1440,25 @@ function mountImperativeHandle<T>(
1440
const effectDeps =
1441
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
1442
1395
- return mountEffectImpl(
1396
- UpdateEffect,
1397
- HookLayout,
1398
- imperativeHandleEffect.bind(null, create, ref),
1399
- effectDeps,
1400
- );
1443
+ if (
1444
+ __DEV__ &&
1445
+ enableDoubleInvokingEffects &&
1446
+ (currentlyRenderingFiber.mode & StrictMode) !== NoMode
1447
+ ) {
1448
+ return mountEffectImpl(
1449
+ UpdateEffect | MountLayoutDevEffect,
1450
+ HookLayout,
1451
+ imperativeHandleEffect.bind(null, create, ref),
1452
+ effectDeps,
1453
+ );
1454
+ } else {
1455
+ return mountEffectImpl(
1456
+ UpdateEffect,
1457
+ HookLayout,
1458
+ imperativeHandleEffect.bind(null, create, ref),
1459
+ effectDeps,
1460
+ );
1461
+ }
1462
}
1463
1464
function updateImperativeHandle<T>(
@@ -1678,7 +1739,17 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
1739
const setId = mountState(id)[1];
1740
1741
if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1681
- currentlyRenderingFiber.flags |= UpdateEffect | PassiveEffect;
1742
+ if (
1743
+ __DEV__ &&
1744
+ enableDoubleInvokingEffects &&
1745
+ (currentlyRenderingFiber.mode & StrictMode) !== NoMode
1746
+ ) {
1747
+ currentlyRenderingFiber.flags |=
1748
+ UpdateEffect | PassiveEffect | MountPassiveDevEffect;
1749
+ } else {
1750
+ currentlyRenderingFiber.flags |= UpdateEffect | PassiveEffect;
1751
+ }
1752
+
1753
pushEffect(
1754
HookHasEffect | HookPassive,
1755
() => {
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+80
-21
@@ -14,6 +14,7 @@ import type {ReactPriorityLevel} from './ReactInternalTypes';
14
import type {Interaction} from 'scheduler/src/Tracing';
15
import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
16
import type {Effect as HookEffect} from './ReactFiberHooks.old';
17
+import type {Flags} from './ReactFiberFlags';
18
import type {StackCursor} from './ReactFiberStack.old';
19
20
import {
@@ -31,6 +32,7 @@ import {
32
enableDebugTracing,
33
enableSchedulingProfiler,
34
enableScopeAPI,
35
+ enableDoubleInvokingEffects,
36
} from 'shared/ReactFeatureFlags';
37
import ReactSharedInternals from 'shared/ReactSharedInternals';
38
import invariant from 'shared/invariant';
@@ -131,6 +133,8 @@ import {
133
HostEffectMask,
134
Hydrating,
135
HydratingAndUpdate,
136
+ MountPassiveDev,
137
+ MountLayoutDev,
138
} from './ReactFiberFlags';
139
import {
140
NoLanePriority,
@@ -191,6 +195,10 @@ import {
195
commitPassiveEffectDurations,
196
commitResetTextContent,
197
isSuspenseBoundaryBeingHidden,
198
+ invokeLayoutEffectMountInDEV,
199
+ invokePassiveEffectMountInDEV,
200
+ invokeLayoutEffectUnmountInDEV,
201
+ invokePassiveEffectUnmountInDEV,
202
} from './ReactFiberCommitWork.old';
203
import {enqueueUpdate} from './ReactUpdateQueue.old';
204
import {resetContextDependencies} from './ReactFiberNewContext.old';
@@ -2113,6 +2121,10 @@ function commitRootImpl(root, renderPriorityLevel) {
2121
pendingPassiveEffectsLanes = lanes;
2122
pendingPassiveEffectsRenderPriority = renderPriorityLevel;
2123
} else {
2124
+ if (__DEV__ && enableDoubleInvokingEffects) {
2125
+ commitDoubleInvokeEffectsInDEV(root, false);
2126
+ }
2127
+
2128
// We are done with the effect chain at this point so let's clear the
2129
// nextEffect pointers to assist with GC. If we have passive effects, we'll
2130
// clear this in flushPassiveEffects.
@@ -2660,6 +2672,29 @@ function flushPassiveEffectsImpl() {
2672
}
2673
}
2674
2675
+ if (enableProfilerTimer && enableProfilerCommitHooks) {
2676
+ const profilerEffects = pendingPassiveProfilerEffects;
2677
+ pendingPassiveProfilerEffects = [];
2678
+ for (let i = 0; i < profilerEffects.length; i++) {
2679
+ const fiber = ((profilerEffects[i]: any): Fiber);
2680
+ commitPassiveEffectDurations(root, fiber);
2681
+ }
2682
+ }
2683
+
2684
+ if (__DEV__) {
2685
+ if (enableDebugTracing) {
2686
+ logPassiveEffectsStopped();
2687
+ }
2688
+ }
2689
+
2690
+ if (enableSchedulingProfiler) {
2691
+ markPassiveEffectsStopped();
2692
+ }
2693
+
2694
+ if (__DEV__ && enableDoubleInvokingEffects) {
2695
+ commitDoubleInvokeEffectsInDEV(root, true);
2696
+ }
2697
+
2698
// Note: This currently assumes there are no passive effects on the root fiber
2699
// because the root is not part of its own effect list.
2700
// This could change in the future.
@@ -2674,13 +2709,8 @@ function flushPassiveEffectsImpl() {
2709
effect = nextNextEffect;
2710
}
2711
2677
- if (enableProfilerTimer && enableProfilerCommitHooks) {
2678
- const profilerEffects = pendingPassiveProfilerEffects;
2679
- pendingPassiveProfilerEffects = [];
2680
- for (let i = 0; i < profilerEffects.length; i++) {
2681
- const fiber = ((profilerEffects[i]: any): Fiber);
2682
- commitPassiveEffectDurations(root, fiber);
2683
- }
2712
+ if (__DEV__) {
2713
+ isFlushingPassiveEffects = false;
2714
}
2715
2716
if (enableSchedulerTracing) {
@@ -2688,20 +2718,6 @@ function flushPassiveEffectsImpl() {
2718
finishPendingInteractions(root, lanes);
2719
}
2720
2691
- if (__DEV__) {
2692
- isFlushingPassiveEffects = false;
2693
- }
2694
-
2695
- if (__DEV__) {
2696
- if (enableDebugTracing) {
2697
- logPassiveEffectsStopped();
2698
- }
2699
- }
2700
-
2701
- if (enableSchedulingProfiler) {
2702
- markPassiveEffectsStopped();
2703
- }
2704
-
2721
executionContext = prevExecutionContext;
2722
2723
flushSyncCallbackQueue();
@@ -2985,6 +3001,49 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
3001
}
3002
}
3003
3004
+function commitDoubleInvokeEffectsInDEV(
3005
+ root: FiberRoot,
3006
+ hasPassiveEffects: boolean,
3007
+) {
3008
+ if (__DEV__ && enableDoubleInvokingEffects) {
3009
+ invokeEffectsInDev(root, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
3010
+ if (hasPassiveEffects) {
3011
+ invokeEffectsInDev(
3012
+ root,
3013
+ MountPassiveDev,
3014
+ invokePassiveEffectUnmountInDEV,
3015
+ );
3016
+ }
3017
+
3018
+ invokeEffectsInDev(root, MountLayoutDev, invokeLayoutEffectMountInDEV);
3019
+ if (hasPassiveEffects) {
3020
+ invokeEffectsInDev(root, MountPassiveDev, invokePassiveEffectMountInDEV);
3021
+ }
3022
+ }
3023
+}
3024
+
3025
+function invokeEffectsInDev(
3026
+ root: FiberRoot,
3027
+ fiberFlags: Flags,
3028
+ invokeEffectFn: (fiber: Fiber) => void,
3029
+): void {
3030
+ if (__DEV__ && enableDoubleInvokingEffects) {
3031
+ // Note: This currently assumes there are no passive effects on the root fiber
3032
+ // because the root is not part of its own effect list.
3033
+ // This could change in the future.
3034
+ let currentEffect = root.current.firstEffect;
3035
+ while (currentEffect !== null) {
3036
+ const flags = currentEffect.flags;
3037
+ if ((flags & fiberFlags) !== NoFlags) {
3038
+ setCurrentDebugFiberInDEV(currentEffect);
3039
+ invokeEffectFn(currentEffect);
3040
+ resetCurrentDebugFiberInDEV();
3041
+ }
3042
+ currentEffect = currentEffect.nextEffect;
3043
+ }
3044
+ }
3045
+}
3046
+
3047
let didWarnStateUpdateForNotYetMountedComponent: Set<string> | null = null;
3048
function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
3049
if (__DEV__) {
packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.internal.js
new
+771
@@ -0,0 +1,771 @@
1
+/**
2
+ * Copyright (c) Facebook, Inc. and its affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ * @emails react-core
8
+ */
9
+
10
+'use strict';
11
+
12
+let React;
13
+let ReactFeatureFlags;
14
+let ReactNoop;
15
+let Scheduler;
16
+
17
+function shouldDoubleInvokingEffects() {
18
+ // For now, this feature only exists in the old fork (while the new fork is being bisected).
19
+ // Eventually we'll land it in both forks.
20
+ return __DEV__ && !__VARIANT__;
21
+}
22
+
23
+describe('ReactDoubleInvokeEvents', () => {
24
+ beforeEach(() => {
25
+ jest.resetModules();
26
+ React = require('react');
27
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
28
+ ReactNoop = require('react-noop-renderer');
29
+ Scheduler = require('scheduler');
30
+
31
+ ReactFeatureFlags.enableDoubleInvokingEffects = shouldDoubleInvokingEffects();
32
+ });
33
+
34
+ it('should not double invoke effects outside of StrictMode', () => {
35
+ function App({text}) {
36
+ React.useEffect(() => {
37
+ Scheduler.unstable_yieldValue('useEffect mount');
38
+ return () => Scheduler.unstable_yieldValue('useEffect unmount');
39
+ });
40
+
41
+ React.useLayoutEffect(() => {
42
+ Scheduler.unstable_yieldValue('useLayoutEffect mount');
43
+ return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
44
+ });
45
+
46
+ return text;
47
+ }
48
+
49
+ ReactNoop.act(() => {
50
+ ReactNoop.renderLegacySyncRoot(<App text={'mount'} />);
51
+ });
52
+
53
+ expect(Scheduler).toHaveYielded([
54
+ 'useLayoutEffect mount',
55
+ 'useEffect mount',
56
+ ]);
57
+ });
58
+
59
+ it('should double invoke effects only within StrictMode subtrees', () => {
60
+ function ComponentWithEffects({label}) {
61
+ React.useEffect(() => {
62
+ Scheduler.unstable_yieldValue(`useEffect mount "${label}"`);
63
+ return () =>
64
+ Scheduler.unstable_yieldValue(`useEffect unmount "${label}"`);
65
+ });
66
+
67
+ React.useLayoutEffect(() => {
68
+ Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);
69
+ return () =>
70
+ Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);
71
+ });
72
+
73
+ return label;
74
+ }
75
+
76
+ ReactNoop.act(() => {
77
+ ReactNoop.renderLegacySyncRoot(
78
+ <>
79
+ <ComponentWithEffects label={'loose'} />
80
+ <React.StrictMode>
81
+ <ComponentWithEffects label={'strict'} />
82
+ </React.StrictMode>
83
+ </>,
84
+ );
85
+ });
86
+
87
+ if (shouldDoubleInvokingEffects()) {
88
+ expect(Scheduler).toHaveYielded([
89
+ 'useLayoutEffect mount "loose"',
90
+ 'useLayoutEffect mount "strict"',
91
+ 'useEffect mount "loose"',
92
+ 'useEffect mount "strict"',
93
+
94
+ 'useLayoutEffect unmount "strict"',
95
+ 'useEffect unmount "strict"',
96
+ 'useLayoutEffect mount "strict"',
97
+ 'useEffect mount "strict"',
98
+ ]);
99
+ } else {
100
+ expect(Scheduler).toHaveYielded([
101
+ 'useLayoutEffect mount "loose"',
102
+ 'useLayoutEffect mount "strict"',
103
+ 'useEffect mount "loose"',
104
+ 'useEffect mount "strict"',
105
+ ]);
106
+ }
107
+ });
108
+
109
+ it('should flush double-invoked effects within the same frame as layout effects if there are no passive effects', () => {
110
+ function ComponentWithEffects({label}) {
111
+ React.useLayoutEffect(() => {
112
+ Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);
113
+ return () =>
114
+ Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);
115
+ });
116
+
117
+ return label;
118
+ }
119
+
120
+ ReactNoop.act(() => {
121
+ ReactNoop.renderLegacySyncRoot(
122
+ <React.StrictMode>
123
+ <ComponentWithEffects label={'one'} />
124
+ </React.StrictMode>,
125
+ );
126
+
127
+ if (shouldDoubleInvokingEffects()) {
128
+ expect(Scheduler).toFlushUntilNextPaint([
129
+ 'useLayoutEffect mount "one"',
130
+ 'useLayoutEffect unmount "one"',
131
+ 'useLayoutEffect mount "one"',
132
+ ]);
133
+ } else {
134
+ expect(Scheduler).toFlushUntilNextPaint([
135
+ 'useLayoutEffect mount "one"',
136
+ ]);
137
+ }
138
+ });
139
+
140
+ ReactNoop.act(() => {
141
+ ReactNoop.renderLegacySyncRoot(
142
+ <React.StrictMode>
143
+ <ComponentWithEffects label={'one'} />
144
+ <ComponentWithEffects label={'two'} />
145
+ </React.StrictMode>,
146
+ );
147
+
148
+ if (shouldDoubleInvokingEffects()) {
149
+ expect(Scheduler).toFlushUntilNextPaint([
150
+ // Cleanup and re-run "one" (and "two") since there is no dependencies array.
151
+ 'useLayoutEffect unmount "one"',
152
+ 'useLayoutEffect mount "one"',
153
+ 'useLayoutEffect mount "two"',
154
+
155
+ // Since "two" is new, it should be double-invoked.
156
+ 'useLayoutEffect unmount "two"',
157
+ 'useLayoutEffect mount "two"',
158
+ ]);
159
+ } else {
160
+ expect(Scheduler).toFlushUntilNextPaint([
161
+ 'useLayoutEffect unmount "one"',
162
+ 'useLayoutEffect mount "one"',
163
+ 'useLayoutEffect mount "two"',
164
+ ]);
165
+ }
166
+ });
167
+ });
168
+
169
+ // This test also verifies that double-invoked effects flush synchronously
170
+ // within the same frame as passive effects.
171
+ it('should double invoke effects only for newly mounted components', () => {
172
+ function ComponentWithEffects({label}) {
173
+ React.useEffect(() => {
174
+ Scheduler.unstable_yieldValue(`useEffect mount "${label}"`);
175
+ return () =>
176
+ Scheduler.unstable_yieldValue(`useEffect unmount "${label}"`);
177
+ });
178
+
179
+ React.useLayoutEffect(() => {
180
+ Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);
181
+ return () =>
182
+ Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);
183
+ });
184
+
185
+ return label;
186
+ }
187
+
188
+ ReactNoop.act(() => {
189
+ ReactNoop.renderLegacySyncRoot(
190
+ <React.StrictMode>
191
+ <ComponentWithEffects label={'one'} />
192
+ </React.StrictMode>,
193
+ );
194
+
195
+ if (shouldDoubleInvokingEffects()) {
196
+ expect(Scheduler).toFlushAndYieldThrough([
197
+ 'useLayoutEffect mount "one"',
198
+ ]);
199
+ expect(Scheduler).toFlushAndYield([
200
+ 'useEffect mount "one"',
201
+ 'useLayoutEffect unmount "one"',
202
+ 'useEffect unmount "one"',
203
+ 'useLayoutEffect mount "one"',
204
+ 'useEffect mount "one"',
205
+ ]);
206
+ } else {
207
+ expect(Scheduler).toFlushAndYieldThrough([
208
+ 'useLayoutEffect mount "one"',
209
+ ]);
210
+ expect(Scheduler).toFlushAndYield(['useEffect mount "one"']);
211
+ }
212
+ });
213
+
214
+ ReactNoop.act(() => {
215
+ ReactNoop.renderLegacySyncRoot(
216
+ <React.StrictMode>
217
+ <ComponentWithEffects label={'one'} />
218
+ <ComponentWithEffects label={'two'} />
219
+ </React.StrictMode>,
220
+ );
221
+
222
+ if (shouldDoubleInvokingEffects()) {
223
+ expect(Scheduler).toFlushAndYieldThrough([
224
+ // Cleanup and re-run "one" (and "two") since there is no dependencies array.
225
+ 'useLayoutEffect unmount "one"',
226
+ 'useLayoutEffect mount "one"',
227
+ 'useLayoutEffect mount "two"',
228
+ ]);
229
+ expect(Scheduler).toFlushAndYield([
230
+ 'useEffect unmount "one"',
231
+ 'useEffect mount "one"',
232
+ 'useEffect mount "two"',
233
+
234
+ // Since "two" is new, it should be double-invoked.
235
+ 'useLayoutEffect unmount "two"',
236
+ 'useEffect unmount "two"',
237
+ 'useLayoutEffect mount "two"',
238
+ 'useEffect mount "two"',
239
+ ]);
240
+ } else {
241
+ expect(Scheduler).toFlushAndYieldThrough([
242
+ 'useLayoutEffect unmount "one"',
243
+ 'useLayoutEffect mount "one"',
244
+ 'useLayoutEffect mount "two"',
245
+ ]);
246
+ expect(Scheduler).toFlushAndYield([
247
+ 'useEffect unmount "one"',
248
+ 'useEffect mount "one"',
249
+ 'useEffect mount "two"',
250
+ ]);
251
+ }
252
+ });
253
+ });
254
+
255
+ it('double invoking for effects for modern roots', () => {
256
+ function App({text}) {
257
+ React.useEffect(() => {
258
+ Scheduler.unstable_yieldValue('useEffect mount');
259
+ return () => Scheduler.unstable_yieldValue('useEffect unmount');
260
+ });
261
+
262
+ React.useLayoutEffect(() => {
263
+ Scheduler.unstable_yieldValue('useLayoutEffect mount');
264
+ return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
265
+ });
266
+
267
+ return text;
268
+ }
269
+ ReactNoop.act(() => {
270
+ ReactNoop.render(<App text={'mount'} />);
271
+ });
272
+
273
+ if (shouldDoubleInvokingEffects()) {
274
+ expect(Scheduler).toHaveYielded([
275
+ 'useLayoutEffect mount',
276
+ 'useEffect mount',
277
+ 'useLayoutEffect unmount',
278
+ 'useEffect unmount',
279
+ 'useLayoutEffect mount',
280
+ 'useEffect mount',
281
+ ]);
282
+ } else {
283
+ expect(Scheduler).toHaveYielded([
284
+ 'useLayoutEffect mount',
285
+ 'useEffect mount',
286
+ ]);
287
+ }
288
+
289
+ ReactNoop.act(() => {
290
+ ReactNoop.render(<App text={'update'} />);
291
+ });
292
+
293
+ expect(Scheduler).toHaveYielded([
294
+ 'useLayoutEffect unmount',
295
+ 'useLayoutEffect mount',
296
+ 'useEffect unmount',
297
+ 'useEffect mount',
298
+ ]);
299
+
300
+ ReactNoop.act(() => {
301
+ ReactNoop.render(null);
302
+ });
303
+
304
+ expect(Scheduler).toHaveYielded([
305
+ 'useLayoutEffect unmount',
306
+ 'useEffect unmount',
307
+ ]);
308
+ });
309
+
310
+ it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {
311
+ function App({text}) {
312
+ React.useEffect(() => {
313
+ Scheduler.unstable_yieldValue('useEffect One mount');
314
+ return () => Scheduler.unstable_yieldValue('useEffect One unmount');
315
+ });
316
+
317
+ React.useEffect(() => {
318
+ Scheduler.unstable_yieldValue('useEffect Two mount');
319
+ return () => Scheduler.unstable_yieldValue('useEffect Two unmount');
320
+ });
321
+
322
+ return text;
323
+ }
324
+
325
+ ReactNoop.act(() => {
326
+ ReactNoop.render(<App text={'mount'} />);
327
+ });
328
+
329
+ if (shouldDoubleInvokingEffects()) {
330
+ expect(Scheduler).toHaveYielded([
331
+ 'useEffect One mount',
332
+ 'useEffect Two mount',
333
+ 'useEffect One unmount',
334
+ 'useEffect Two unmount',
335
+ 'useEffect One mount',
336
+ 'useEffect Two mount',
337
+ ]);
338
+ } else {
339
+ expect(Scheduler).toHaveYielded([
340
+ 'useEffect One mount',
341
+ 'useEffect Two mount',
342
+ ]);
343
+ }
344
+
345
+ ReactNoop.act(() => {
346
+ ReactNoop.render(<App text={'update'} />);
347
+ });
348
+
349
+ expect(Scheduler).toHaveYielded([
350
+ 'useEffect One unmount',
351
+ 'useEffect Two unmount',
352
+ 'useEffect One mount',
353
+ 'useEffect Two mount',
354
+ ]);
355
+
356
+ ReactNoop.act(() => {
357
+ ReactNoop.render(null);
358
+ });
359
+
360
+ expect(Scheduler).toHaveYielded([
361
+ 'useEffect One unmount',
362
+ 'useEffect Two unmount',
363
+ ]);
364
+ });
365
+
366
+ it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {
367
+ function App({text}) {
368
+ React.useLayoutEffect(() => {
369
+ Scheduler.unstable_yieldValue('useLayoutEffect One mount');
370
+ return () =>
371
+ Scheduler.unstable_yieldValue('useLayoutEffect One unmount');
372
+ });
373
+
374
+ React.useLayoutEffect(() => {
375
+ Scheduler.unstable_yieldValue('useLayoutEffect Two mount');
376
+ return () =>
377
+ Scheduler.unstable_yieldValue('useLayoutEffect Two unmount');
378
+ });
379
+
380
+ return text;
381
+ }
382
+
383
+ ReactNoop.act(() => {
384
+ ReactNoop.render(<App text={'mount'} />);
385
+ });
386
+
387
+ if (shouldDoubleInvokingEffects()) {
388
+ expect(Scheduler).toHaveYielded([
389
+ 'useLayoutEffect One mount',
390
+ 'useLayoutEffect Two mount',
391
+ 'useLayoutEffect One unmount',
392
+ 'useLayoutEffect Two unmount',
393
+ 'useLayoutEffect One mount',
394
+ 'useLayoutEffect Two mount',
395
+ ]);
396
+ } else {
397
+ expect(Scheduler).toHaveYielded([
398
+ 'useLayoutEffect One mount',
399
+ 'useLayoutEffect Two mount',
400
+ ]);
401
+ }
402
+
403
+ ReactNoop.act(() => {
404
+ ReactNoop.render(<App text={'update'} />);
405
+ });
406
+
407
+ expect(Scheduler).toHaveYielded([
408
+ 'useLayoutEffect One unmount',
409
+ 'useLayoutEffect Two unmount',
410
+ 'useLayoutEffect One mount',
411
+ 'useLayoutEffect Two mount',
412
+ ]);
413
+
414
+ ReactNoop.act(() => {
415
+ ReactNoop.render(null);
416
+ });
417
+
418
+ expect(Scheduler).toHaveYielded([
419
+ 'useLayoutEffect One unmount',
420
+ 'useLayoutEffect Two unmount',
421
+ ]);
422
+ });
423
+
424
+ it('useEffect and useLayoutEffect is called twice when there is no unmount', () => {
425
+ function App({text}) {
426
+ React.useEffect(() => {
427
+ Scheduler.unstable_yieldValue('useEffect mount');
428
+ });
429
+
430
+ React.useLayoutEffect(() => {
431
+ Scheduler.unstable_yieldValue('useLayoutEffect mount');
432
+ });
433
+
434
+ return text;
435
+ }
436
+
437
+ ReactNoop.act(() => {
438
+ ReactNoop.render(<App text={'mount'} />);
439
+ });
440
+
441
+ if (shouldDoubleInvokingEffects()) {
442
+ expect(Scheduler).toHaveYielded([
443
+ 'useLayoutEffect mount',
444
+ 'useEffect mount',
445
+ 'useLayoutEffect mount',
446
+ 'useEffect mount',
447
+ ]);
448
+ } else {
449
+ expect(Scheduler).toHaveYielded([
450
+ 'useLayoutEffect mount',
451
+ 'useEffect mount',
452
+ ]);
453
+ }
454
+
455
+ ReactNoop.act(() => {
456
+ ReactNoop.render(<App text={'update'} />);
457
+ });
458
+
459
+ expect(Scheduler).toHaveYielded([
460
+ 'useLayoutEffect mount',
461
+ 'useEffect mount',
462
+ ]);
463
+
464
+ ReactNoop.act(() => {
465
+ ReactNoop.render(null);
466
+ });
467
+
468
+ expect(Scheduler).toHaveYielded([]);
469
+ });
470
+
471
+ it('passes the right context to class component lifecycles', () => {
472
+ class App extends React.PureComponent {
473
+ test() {}
474
+
475
+ componentDidMount() {
476
+ this.test();
477
+ Scheduler.unstable_yieldValue('componentDidMount');
478
+ }
479
+
480
+ componentDidUpdate() {
481
+ this.test();
482
+ Scheduler.unstable_yieldValue('componentDidUpdate');
483
+ }
484
+
485
+ componentWillUnmount() {
486
+ this.test();
487
+ Scheduler.unstable_yieldValue('componentWillUnmount');
488
+ }
489
+
490
+ render() {
491
+ return null;
492
+ }
493
+ }
494
+
495
+ ReactNoop.act(() => {
496
+ ReactNoop.render(<App />);
497
+ });
498
+
499
+ if (shouldDoubleInvokingEffects()) {
500
+ expect(Scheduler).toHaveYielded([
501
+ 'componentDidMount',
502
+ 'componentWillUnmount',
503
+ 'componentDidMount',
504
+ ]);
505
+ } else {
506
+ expect(Scheduler).toHaveYielded(['componentDidMount']);
507
+ }
508
+ });
509
+
510
+ it('double invoking works for class components', () => {
511
+ class App extends React.PureComponent {
512
+ componentDidMount() {
513
+ Scheduler.unstable_yieldValue('componentDidMount');
514
+ }
515
+
516
+ componentDidUpdate() {
517
+ Scheduler.unstable_yieldValue('componentDidUpdate');
518
+ }
519
+
520
+ componentWillUnmount() {
521
+ Scheduler.unstable_yieldValue('componentWillUnmount');
522
+ }
523
+
524
+ render() {
525
+ return this.props.text;
526
+ }
527
+ }
528
+
529
+ ReactNoop.act(() => {
530
+ ReactNoop.render(<App text={'mount'} />);
531
+ });
532
+
533
+ if (shouldDoubleInvokingEffects()) {
534
+ expect(Scheduler).toHaveYielded([
535
+ 'componentDidMount',
536
+ 'componentWillUnmount',
537
+ 'componentDidMount',
538
+ ]);
539
+ } else {
540
+ expect(Scheduler).toHaveYielded(['componentDidMount']);
541
+ }
542
+
543
+ ReactNoop.act(() => {
544
+ ReactNoop.render(<App text={'update'} />);
545
+ });
546
+
547
+ expect(Scheduler).toHaveYielded(['componentDidUpdate']);
548
+
549
+ ReactNoop.act(() => {
550
+ ReactNoop.render(null);
551
+ });
552
+
553
+ expect(Scheduler).toHaveYielded(['componentWillUnmount']);
554
+ });
555
+
556
+ it('double flushing passive effects only results in one double invoke', () => {
557
+ function App({text}) {
558
+ const [state, setState] = React.useState(0);
559
+ React.useEffect(() => {
560
+ if (state !== 1) {
561
+ setState(1);
562
+ }
563
+ Scheduler.unstable_yieldValue('useEffect mount');
564
+ return () => Scheduler.unstable_yieldValue('useEffect unmount');
565
+ });
566
+
567
+ React.useLayoutEffect(() => {
568
+ Scheduler.unstable_yieldValue('useLayoutEffect mount');
569
+ return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
570
+ });
571
+
572
+ Scheduler.unstable_yieldValue(text);
573
+ return text;
574
+ }
575
+
576
+ ReactNoop.act(() => {
577
+ ReactNoop.render(<App text={'mount'} />);
578
+ });
579
+
580
+ if (shouldDoubleInvokingEffects()) {
581
+ expect(Scheduler).toHaveYielded([
582
+ 'mount',
583
+ 'useLayoutEffect mount',
584
+ 'useEffect mount',
585
+ 'useLayoutEffect unmount',
586
+ 'useEffect unmount',
587
+ 'useLayoutEffect mount',
588
+ 'useEffect mount',
589
+ 'mount',
590
+ 'useLayoutEffect unmount',
591
+ 'useLayoutEffect mount',
592
+ 'useEffect unmount',
593
+ 'useEffect mount',
594
+ ]);
595
+ } else {
596
+ expect(Scheduler).toHaveYielded([
597
+ 'mount',
598
+ 'useLayoutEffect mount',
599
+ 'useEffect mount',
600
+ 'mount',
601
+ 'useLayoutEffect unmount',
602
+ 'useLayoutEffect mount',
603
+ 'useEffect unmount',
604
+ 'useEffect mount',
605
+ ]);
606
+ }
607
+ });
608
+
609
+ it('newly mounted components after initial mount get double invoked', () => {
610
+ let _setShowChild;
611
+ function Child() {
612
+ React.useEffect(() => {
613
+ Scheduler.unstable_yieldValue('Child useEffect mount');
614
+ return () => Scheduler.unstable_yieldValue('Child useEffect unmount');
615
+ });
616
+ React.useLayoutEffect(() => {
617
+ Scheduler.unstable_yieldValue('Child useLayoutEffect mount');
618
+ return () =>
619
+ Scheduler.unstable_yieldValue('Child useLayoutEffect unmount');
620
+ });
621
+
622
+ return null;
623
+ }
624
+
625
+ function App() {
626
+ const [showChild, setShowChild] = React.useState(false);
627
+ _setShowChild = setShowChild;
628
+ React.useEffect(() => {
629
+ Scheduler.unstable_yieldValue('App useEffect mount');
630
+ return () => Scheduler.unstable_yieldValue('App useEffect unmount');
631
+ });
632
+ React.useLayoutEffect(() => {
633
+ Scheduler.unstable_yieldValue('App useLayoutEffect mount');
634
+ return () =>
635
+ Scheduler.unstable_yieldValue('App useLayoutEffect unmount');
636
+ });
637
+
638
+ return showChild && <Child />;
639
+ }
640
+
641
+ ReactNoop.act(() => {
642
+ ReactNoop.render(<App />);
643
+ });
644
+
645
+ if (shouldDoubleInvokingEffects()) {
646
+ expect(Scheduler).toHaveYielded([
647
+ 'App useLayoutEffect mount',
648
+ 'App useEffect mount',
649
+ 'App useLayoutEffect unmount',
650
+ 'App useEffect unmount',
651
+ 'App useLayoutEffect mount',
652
+ 'App useEffect mount',
653
+ ]);
654
+ } else {
655
+ expect(Scheduler).toHaveYielded([
656
+ 'App useLayoutEffect mount',
657
+ 'App useEffect mount',
658
+ ]);
659
+ }
660
+
661
+ ReactNoop.act(() => {
662
+ _setShowChild(true);
663
+ });
664
+
665
+ if (shouldDoubleInvokingEffects()) {
666
+ expect(Scheduler).toHaveYielded([
667
+ 'App useLayoutEffect unmount',
668
+ 'Child useLayoutEffect mount',
669
+ 'App useLayoutEffect mount',
670
+ 'App useEffect unmount',
671
+ 'Child useEffect mount',
672
+ 'App useEffect mount',
673
+ 'Child useLayoutEffect unmount',
674
+ 'Child useEffect unmount',
675
+ 'Child useLayoutEffect mount',
676
+ 'Child useEffect mount',
677
+ ]);
678
+ } else {
679
+ expect(Scheduler).toHaveYielded([
680
+ 'App useLayoutEffect unmount',
681
+ 'Child useLayoutEffect mount',
682
+ 'App useLayoutEffect mount',
683
+ 'App useEffect unmount',
684
+ 'Child useEffect mount',
685
+ 'App useEffect mount',
686
+ ]);
687
+ }
688
+ });
689
+
690
+ it('classes and functions are double invoked together correctly', () => {
691
+ class ClassChild extends React.PureComponent {
692
+ componentDidMount() {
693
+ Scheduler.unstable_yieldValue('componentDidMount');
694
+ }
695
+
696
+ componentWillUnmount() {
697
+ Scheduler.unstable_yieldValue('componentWillUnmount');
698
+ }
699
+
700
+ render() {
701
+ return this.props.text;
702
+ }
703
+ }
704
+
705
+ function FunctionChild({text}) {
706
+ React.useEffect(() => {
707
+ Scheduler.unstable_yieldValue('useEffect mount');
708
+ return () => Scheduler.unstable_yieldValue('useEffect unmount');
709
+ });
710
+ React.useLayoutEffect(() => {
711
+ Scheduler.unstable_yieldValue('useLayoutEffect mount');
712
+ return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
713
+ });
714
+ return text;
715
+ }
716
+
717
+ function App({text}) {
718
+ return (
719
+ <>
720
+ <ClassChild text={text} />
721
+ <FunctionChild text={text} />
722
+ </>
723
+ );
724
+ }
725
+
726
+ ReactNoop.act(() => {
727
+ ReactNoop.render(<App text={'mount'} />);
728
+ });
729
+
730
+ if (shouldDoubleInvokingEffects()) {
731
+ expect(Scheduler).toHaveYielded([
732
+ 'componentDidMount',
733
+ 'useLayoutEffect mount',
734
+ 'useEffect mount',
735
+ 'componentWillUnmount',
736
+ 'useLayoutEffect unmount',
737
+ 'useEffect unmount',
738
+ 'componentDidMount',
739
+ 'useLayoutEffect mount',
740
+ 'useEffect mount',
741
+ ]);
742
+ } else {
743
+ expect(Scheduler).toHaveYielded([
744
+ 'componentDidMount',
745
+ 'useLayoutEffect mount',
746
+ 'useEffect mount',
747
+ ]);
748
+ }
749
+
750
+ ReactNoop.act(() => {
751
+ ReactNoop.render(<App text={'mount'} />);
752
+ });
753
+
754
+ expect(Scheduler).toHaveYielded([
755
+ 'useLayoutEffect unmount',
756
+ 'useLayoutEffect mount',
757
+ 'useEffect unmount',
758
+ 'useEffect mount',
759
+ ]);
760
+
761
+ ReactNoop.act(() => {
762
+ ReactNoop.render(null);
763
+ });
764
+
765
+ expect(Scheduler).toHaveYielded([
766
+ 'componentWillUnmount',
767
+ 'useLayoutEffect unmount',
768
+ 'useEffect unmount',
769
+ ]);
770
+ });
771
+});
packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.js
+1
-3
@@ -26,9 +26,7 @@ describe('ReactDoubleInvokeEvents', () => {
26
function supportsDoubleInvokeEffects() {
27
return gate(
28
flags =>
29
- flags.build === 'development' &&
30
- flags.enableDoubleInvokingEffects &&
31
- flags.dfsEffectsRefactor,
29
+ flags.build === 'development' && flags.enableDoubleInvokingEffects,
30
);
31
}
32
packages/react/src/__tests__/ReactProfiler-test.internal.js
+6
-4
@@ -4874,10 +4874,8 @@ describe('Profiler', () => {
4874
});
4875
4876
if (__DEV__) {
4877
- // @gate dfsEffectsRefactor
4878
- // @gate enableDoubleInvokingEffects
4877
it('double invoking does not disconnect wrapped async work', () => {
4880
- ReactFeatureFlags.enableDoubleInvokingEffects = true;
4878
+ ReactFeatureFlags.enableDoubleInvokingEffects = !__VARIANT__;
4879
4880
const callback = jest.fn(() => {
4881
const wrappedInteractions = SchedulerTracing.unstable_getCurrent();
@@ -4915,7 +4913,11 @@ describe('Profiler', () => {
4913
4914
jest.runAllTimers();
4915
4918
- expect(callback).toHaveBeenCalledTimes(4); // 2x per effect
4916
+ if (ReactFeatureFlags.enableDoubleInvokingEffects) {
4917
+ expect(callback).toHaveBeenCalledTimes(4); // 2x per effect
4918
+ } else {
4919
+ expect(callback).toHaveBeenCalledTimes(2);
4920
+ }
4921
4922
expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
4923
});