offscreen double invoke effects (#19523)
This PR double invokes effects in __DEV__ mode. We are thinking about unmounting layout and/or passive effects for a hidden tree. To understand potential issues with this, we want to double invoke effects. This PR changes the behavior in DEV when an effect runs from create() to create() -> destroy() -> create(). The effect cleanup function will still be called before the effect runs in both dev and prod. (Note: This change is purely for research for now as it is likely to break real code.) **Note: The change is fully behind a flag and does not affect any of the code on npm.**
Luna Ruan committed
Sep 24, 2020 at 13:42 UTC
c63741fb3daef6c1e8746cbe7d7b07ecb281a9fd
17 files changed
+888
-60
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+22
-5
@@ -12,13 +12,14 @@ import type {Lanes} from './ReactFiberLane';
12
import type {UpdateQueue} from './ReactUpdateQueue.new';
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.new';
25
import {isMounted} from './ReactFiberTreeReflection';
@@ -890,7 +891,11 @@ function mountClassInstance(
891
}
892
893
if (typeof instance.componentDidMount === 'function') {
893
- workInProgress.flags |= Update;
894
+ if (__DEV__ && enableDoubleInvokingEffects) {
895
+ workInProgress.flags |= MountLayoutDev | Update;
896
+ } else {
897
+ workInProgress.flags |= Update;
898
+ }
899
}
900
}
901
@@ -960,7 +965,11 @@ function resumeMountClassInstance(
965
// If an update was already in progress, we should schedule an Update
966
// effect even though we're bailing out, so that cWU/cDU are called.
967
if (typeof instance.componentDidMount === 'function') {
963
- workInProgress.flags |= Update;
968
+ if (__DEV__ && enableDoubleInvokingEffects) {
969
+ workInProgress.flags |= MountLayoutDev | Update;
970
+ } else {
971
+ workInProgress.flags |= Update;
972
+ }
973
}
974
return false;
975
}
@@ -1003,13 +1012,21 @@ function resumeMountClassInstance(
1012
}
1013
}
1014
if (typeof instance.componentDidMount === 'function') {
1006
- workInProgress.flags |= Update;
1015
+ if (__DEV__ && enableDoubleInvokingEffects) {
1016
+ workInProgress.flags |= MountLayoutDev | Update;
1017
+ } else {
1018
+ workInProgress.flags |= Update;
1019
+ }
1020
}
1021
} else {
1022
// If an update was already in progress, we should schedule an Update
1023
// effect even though we're bailing out, so that cWU/cDU are called.
1024
if (typeof instance.componentDidMount === 'function') {
1012
- workInProgress.flags |= Update;
1025
+ if (__DEV__ && enableDoubleInvokingEffects) {
1026
+ workInProgress.flags |= MountLayoutDev | Update;
1027
+ } else {
1028
+ workInProgress.flags |= Update;
1029
+ }
1030
}
1031
1032
// If shouldComponentUpdate returned false, we should still update the
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+135
-5
@@ -35,6 +35,7 @@ import {
35
enableFundamentalAPI,
36
enableSuspenseCallback,
37
enableScopeAPI,
38
+ enableDoubleInvokingEffects,
39
} from 'shared/ReactFeatureFlags';
40
import {
41
FunctionComponent,
@@ -159,7 +160,7 @@ const callComponentWillUnmountWithTimer = function(current, instance) {
160
function safelyCallComponentWillUnmount(
161
current: Fiber,
162
instance: any,
162
- nearestMountedAncestor: Fiber,
163
+ nearestMountedAncestor: Fiber | null,
164
) {
165
if (__DEV__) {
166
invokeGuardedCallback(
@@ -318,7 +319,7 @@ function commitBeforeMutationLifeCycles(
319
}
320
321
function commitHookEffectListUnmount(
321
- tag: HookFlags,
322
+ flags: HookFlags,
323
finishedWork: Fiber,
324
nearestMountedAncestor: Fiber | null,
325
) {
@@ -328,7 +329,7 @@ function commitHookEffectListUnmount(
329
const firstEffect = lastEffect.next;
330
let effect = firstEffect;
331
do {
331
- if ((effect.tag & tag) === tag) {
332
+ if ((effect.tag & flags) === flags) {
333
// Unmount
334
const destroy = effect.destroy;
335
effect.destroy = undefined;
@@ -341,14 +342,14 @@ function commitHookEffectListUnmount(
342
}
343
}
344
344
-function commitHookEffectListMount(tag: HookFlags, finishedWork: Fiber) {
345
+function commitHookEffectListMount(flags: HookFlags, finishedWork: Fiber) {
346
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
347
const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
348
if (lastEffect !== null) {
349
const firstEffect = lastEffect.next;
350
let effect = firstEffect;
351
do {
351
- if ((effect.tag & tag) === tag) {
352
+ if ((effect.tag & flags) === flags) {
353
// Mount
354
const create = effect.create;
355
effect.destroy = create();
@@ -1884,6 +1885,131 @@ function commitPassiveMount(
1885
}
1886
}
1887
1888
+function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
1889
+ if (__DEV__ && enableDoubleInvokingEffects) {
1890
+ switch (fiber.tag) {
1891
+ case FunctionComponent:
1892
+ case ForwardRef:
1893
+ case SimpleMemoComponent:
1894
+ case Block: {
1895
+ invokeGuardedCallback(
1896
+ null,
1897
+ commitHookEffectListMount,
1898
+ null,
1899
+ HookLayout | HookHasEffect,
1900
+ fiber,
1901
+ );
1902
+ if (hasCaughtError()) {
1903
+ const mountError = clearCaughtError();
1904
+ captureCommitPhaseError(fiber, fiber.return, mountError);
1905
+ }
1906
+ break;
1907
+ }
1908
+ case ClassComponent: {
1909
+ const instance = fiber.stateNode;
1910
+ invokeGuardedCallback(null, instance.componentDidMount, null);
1911
+ if (hasCaughtError()) {
1912
+ const mountError = clearCaughtError();
1913
+ captureCommitPhaseError(fiber, fiber.return, mountError);
1914
+ }
1915
+ break;
1916
+ }
1917
+ }
1918
+ }
1919
+}
1920
+
1921
+function invokePassiveEffectMountInDEV(fiber: Fiber): void {
1922
+ if (__DEV__ && enableDoubleInvokingEffects) {
1923
+ switch (fiber.tag) {
1924
+ case FunctionComponent:
1925
+ case ForwardRef:
1926
+ case SimpleMemoComponent:
1927
+ case Block: {
1928
+ invokeGuardedCallback(
1929
+ null,
1930
+ commitHookEffectListMount,
1931
+ null,
1932
+ HookPassive | HookHasEffect,
1933
+ fiber,
1934
+ );
1935
+ if (hasCaughtError()) {
1936
+ const mountError = clearCaughtError();
1937
+ captureCommitPhaseError(fiber, fiber.return, mountError);
1938
+ }
1939
+ break;
1940
+ }
1941
+ }
1942
+ }
1943
+}
1944
+
1945
+function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
1946
+ if (__DEV__ && enableDoubleInvokingEffects) {
1947
+ switch (fiber.tag) {
1948
+ case FunctionComponent:
1949
+ case ForwardRef:
1950
+ case SimpleMemoComponent:
1951
+ case Block: {
1952
+ invokeGuardedCallback(
1953
+ null,
1954
+ commitHookEffectListUnmount,
1955
+ null,
1956
+ HookLayout | HookHasEffect,
1957
+ fiber,
1958
+ fiber.return,
1959
+ );
1960
+ if (hasCaughtError()) {
1961
+ const unmountError = clearCaughtError();
1962
+ captureCommitPhaseError(fiber, fiber.return, unmountError);
1963
+ }
1964
+ break;
1965
+ }
1966
+ case ClassComponent: {
1967
+ const instance = fiber.stateNode;
1968
+ if (typeof instance.componentWillUnmount === 'function') {
1969
+ invokeGuardedCallback(
1970
+ null,
1971
+ safelyCallComponentWillUnmount,
1972
+ null,
1973
+ fiber,
1974
+ instance,
1975
+ fiber.return,
1976
+ );
1977
+ if (hasCaughtError()) {
1978
+ const unmountError = clearCaughtError();
1979
+ captureCommitPhaseError(fiber, fiber.return, unmountError);
1980
+ }
1981
+ }
1982
+ break;
1983
+ }
1984
+ }
1985
+ }
1986
+}
1987
+
1988
+function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
1989
+ if (__DEV__ && enableDoubleInvokingEffects) {
1990
+ switch (fiber.tag) {
1991
+ case FunctionComponent:
1992
+ case ForwardRef:
1993
+ case SimpleMemoComponent:
1994
+ case Block: {
1995
+ invokeGuardedCallback(
1996
+ null,
1997
+ commitHookEffectListUnmount,
1998
+ null,
1999
+ HookPassive | HookHasEffect,
2000
+ fiber,
2001
+ fiber.return,
2002
+ );
2003
+ if (hasCaughtError()) {
2004
+ const unmountError = clearCaughtError();
2005
+ captureCommitPhaseError(fiber, fiber.return, unmountError);
2006
+ }
2007
+ break;
2008
+ }
2009
+ }
2010
+ }
2011
+}
2012
+
2013
export {
2014
commitBeforeMutationLifeCycles,
2015
commitResetTextContent,
@@ -1896,4 +2022,8 @@ export {
2022
commitPassiveUnmount,
2023
commitPassiveUnmountInsideDeletedTree,
2024
commitPassiveMount,
2025
+ invokeLayoutEffectMountInDEV,
2026
+ invokeLayoutEffectUnmountInDEV,
2027
+ invokePassiveEffectMountInDEV,
2028
+ invokePassiveEffectUnmountInDEV,
2029
};
packages/react-reconciler/src/ReactFiberFlags.js
+32
-26
@@ -10,50 +10,56 @@
10
export type Flags = number;
11
12
// Don't change these two values. They're used by React Dev Tools.
13
-export const NoFlags = /* */ 0b0000000000000000;
14
-export const PerformedWork = /* */ 0b0000000000000001;
13
+export const NoFlags = /* */ 0b000000000000000000;
14
+export const PerformedWork = /* */ 0b000000000000000001;
15
16
// You can change the rest (and add more).
17
-export const Placement = /* */ 0b0000000000000010;
18
-export const Update = /* */ 0b0000000000000100;
19
-export const PlacementAndUpdate = /* */ 0b0000000000000110;
20
-export const Deletion = /* */ 0b0000000000001000;
21
-export const ContentReset = /* */ 0b0000000000010000;
22
-export const Callback = /* */ 0b0000000000100000;
23
-export const DidCapture = /* */ 0b0000000001000000;
24
-export const Ref = /* */ 0b0000000010000000;
25
-export const Snapshot = /* */ 0b0000000100000000;
26
-export const Passive = /* */ 0b0000001000000000;
17
+export const Placement = /* */ 0b000000000000000010;
18
+export const Update = /* */ 0b000000000000000100;
19
+export const PlacementAndUpdate = /* */ 0b000000000000000110;
20
+export const Deletion = /* */ 0b000000000000001000;
21
+export const ContentReset = /* */ 0b000000000000010000;
22
+export const Callback = /* */ 0b000000000000100000;
23
+export const DidCapture = /* */ 0b000000000001000000;
24
+export const Ref = /* */ 0b000000000010000000;
25
+export const Snapshot = /* */ 0b000000000100000000;
26
+export const Passive = /* */ 0b000000001000000000;
27
// TODO (effects) Remove this bit once the new reconciler is synced to the old.
28
-export const PassiveUnmountPendingDev = /* */ 0b0010000000000000;
29
-export const Hydrating = /* */ 0b0000010000000000;
30
-export const HydratingAndUpdate = /* */ 0b0000010000000100;
28
+export const PassiveUnmountPendingDev = /* */ 0b000010000000000000;
29
+export const Hydrating = /* */ 0b000000010000000000;
30
+export const HydratingAndUpdate = /* */ 0b000000010000000100;
31
32
// Passive & Update & Callback & Ref & Snapshot
33
-export const LifecycleEffectMask = /* */ 0b0000001110100100;
33
+export const LifecycleEffectMask = /* */ 0b000000001110100100;
34
35
// Union of all host effects
36
-export const HostEffectMask = /* */ 0b0000011111111111;
36
+export const HostEffectMask = /* */ 0b000000011111111111;
37
38
// These are not really side effects, but we still reuse this field.
39
-export const Incomplete = /* */ 0b0000100000000000;
40
-export const ShouldCapture = /* */ 0b0001000000000000;
41
-export const ForceUpdateForLegacySuspense = /* */ 0b0100000000000000;
39
+export const Incomplete = /* */ 0b000000100000000000;
40
+export const ShouldCapture = /* */ 0b000001000000000000;
41
+export const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000;
42
43
// Static tags describe aspects of a fiber that are not specific to a render,
44
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
45
// This enables us to defer more work in the unmount case,
46
// since we can defer traversing the tree during layout to look for Passive effects,
47
// and instead rely on the static flag as a signal that there may be cleanup work.
48
-export const PassiveStatic = /* */ 0b1000000000000000;
48
+export const PassiveStatic = /* */ 0b001000000000000000;
49
50
// Union of side effect groupings as pertains to subtreeFlags
51
-export const BeforeMutationMask = /* */ 0b0000001100001010;
52
-export const MutationMask = /* */ 0b0000010010011110;
53
-export const LayoutMask = /* */ 0b0000000010100100;
54
-export const PassiveMask = /* */ 0b0000001000001000;
51
+export const BeforeMutationMask = /* */ 0b000000001100001010;
52
+export const MutationMask = /* */ 0b000000010010011110;
53
+export const LayoutMask = /* */ 0b000000000010100100;
54
+export const PassiveMask = /* */ 0b000000001000001000;
55
56
// Union of tags that don't get reset on clones.
57
// This allows certain concepts to persist without recalculting them,
58
// e.g. whether a subtree contains passive effects or portals.
59
-export const StaticMask = /* */ 0b1000000000000000;
59
+export const StaticMask = /* */ 0b001000000000000000;
60
+
61
+// These flags allow us to traverse to fibers that have effects on mount
62
+// without traversing the entire tree after every commit for
63
+// double invoking
64
+export const MountLayoutDev = /* */ 0b010000000000000000;
65
+export const MountPassiveDev = /* */ 0b100000000000000000;
packages/react-reconciler/src/ReactFiberHooks.new.js
+60
-15
@@ -26,6 +26,7 @@ import {
26
enableSchedulingProfiler,
27
enableNewReconciler,
28
decoupleUpdatePriorityFromScheduler,
29
+ enableDoubleInvokingEffects,
30
} from 'shared/ReactFeatureFlags';
31
32
import {NoMode, BlockingMode, DebugTracingMode} from './ReactTypeOfMode';
@@ -48,6 +49,8 @@ import {
49
Update as UpdateEffect,
50
Passive as PassiveEffect,
51
PassiveStatic as PassiveStaticEffect,
52
+ MountLayoutDev as MountLayoutDevEffect,
53
+ MountPassiveDev as MountPassiveDevEffect,
54
} from './ReactFiberFlags';
55
import {
56
HasEffect as HookHasEffect,
@@ -482,7 +485,16 @@ export function bailoutHooks(
485
lanes: Lanes,
486
) {
487
workInProgress.updateQueue = current.updateQueue;
485
- workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
488
+ if (__DEV__ && enableDoubleInvokingEffects) {
489
+ workInProgress.flags &= ~(
490
+ MountPassiveDevEffect |
491
+ PassiveEffect |
492
+ MountLayoutDevEffect |
493
+ UpdateEffect
494
+ );
495
+ } else {
496
+ workInProgress.flags &= ~(PassiveEffect | UpdateEffect);
497
+ }
498
current.lanes = removeLanes(current.lanes, lanes);
499
}
500
@@ -1240,12 +1252,22 @@ function mountEffect(
1252
warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
1253
}
1254
}
1243
- return mountEffectImpl(
1244
- PassiveEffect | PassiveStaticEffect,
1245
- HookPassive,
1246
- create,
1247
- deps,
1248
- );
1255
+
1256
+ if (__DEV__ && enableDoubleInvokingEffects) {
1257
+ return mountEffectImpl(
1258
+ MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
1259
+ HookPassive,
1260
+ create,
1261
+ deps,
1262
+ );
1263
+ } else {
1264
+ return mountEffectImpl(
1265
+ PassiveEffect | PassiveStaticEffect,
1266
+ HookPassive,
1267
+ create,
1268
+ deps,
1269
+ );
1270
+ }
1271
}
1272
1273
function updateEffect(
@@ -1265,7 +1287,16 @@ function mountLayoutEffect(
1287
create: () => (() => void) | void,
1288
deps: Array<mixed> | void | null,
1289
): void {
1268
- return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1290
+ if (__DEV__ && enableDoubleInvokingEffects) {
1291
+ return mountEffectImpl(
1292
+ MountLayoutDevEffect | UpdateEffect,
1293
+ HookLayout,
1294
+ create,
1295
+ deps,
1296
+ );
1297
+ } else {
1298
+ return mountEffectImpl(UpdateEffect, HookLayout, create, deps);
1299
+ }
1300
}
1301
1302
function updateLayoutEffect(
@@ -1324,12 +1355,21 @@ function mountImperativeHandle<T>(
1355
const effectDeps =
1356
deps !== null && deps !== undefined ? deps.concat([ref]) : null;
1357
1327
- return mountEffectImpl(
1328
- UpdateEffect,
1329
- HookLayout,
1330
- imperativeHandleEffect.bind(null, create, ref),
1331
- effectDeps,
1332
- );
1358
+ if (__DEV__ && enableDoubleInvokingEffects) {
1359
+ return mountEffectImpl(
1360
+ MountLayoutDevEffect | UpdateEffect,
1361
+ HookLayout,
1362
+ imperativeHandleEffect.bind(null, create, ref),
1363
+ effectDeps,
1364
+ );
1365
+ } else {
1366
+ return mountEffectImpl(
1367
+ UpdateEffect,
1368
+ HookLayout,
1369
+ imperativeHandleEffect.bind(null, create, ref),
1370
+ effectDeps,
1371
+ );
1372
+ }
1373
}
1374
1375
function updateImperativeHandle<T>(
@@ -1610,7 +1650,12 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
1650
const setId = mountState(id)[1];
1651
1652
if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1613
- currentlyRenderingFiber.flags |= PassiveEffect | PassiveStaticEffect;
1653
+ if (__DEV__ && enableDoubleInvokingEffects) {
1654
+ currentlyRenderingFiber.flags |=
1655
+ MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect;
1656
+ } else {
1657
+ currentlyRenderingFiber.flags |= PassiveEffect | PassiveStaticEffect;
1658
+ }
1659
pushEffect(
1660
HookHasEffect | HookPassive,
1661
() => {
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+73
-9
@@ -15,6 +15,7 @@ import type {Interaction} from 'scheduler/src/Tracing';
15
import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
16
import type {StackCursor} from './ReactFiberStack.new';
17
import type {FunctionComponentUpdateQueue} from './ReactFiberHooks.new';
18
+import type {Flags} from './ReactFiberFlags';
19
20
import {
21
warnAboutDeprecatedLifecycles,
@@ -30,6 +31,7 @@ import {
31
enableScopeAPI,
32
skipUnmountedBoundaries,
33
disableSchedulerTimeoutInWorkLoop,
34
+ enableDoubleInvokingEffects,
35
} from 'shared/ReactFeatureFlags';
36
import ReactSharedInternals from 'shared/ReactSharedInternals';
37
import invariant from 'shared/invariant';
@@ -136,6 +138,8 @@ import {
138
MutationMask,
139
LayoutMask,
140
PassiveMask,
141
+ MountPassiveDev,
142
+ MountLayoutDev,
143
} from './ReactFiberFlags';
144
import {
145
NoLanePriority,
@@ -198,6 +202,10 @@ import {
202
commitAttachRef,
203
commitResetTextContent,
204
isSuspenseBoundaryBeingHidden,
205
+ invokeLayoutEffectMountInDEV,
206
+ invokePassiveEffectMountInDEV,
207
+ invokeLayoutEffectUnmountInDEV,
208
+ invokePassiveEffectUnmountInDEV,
209
} from './ReactFiberCommitWork.new';
210
import {enqueueUpdate} from './ReactUpdateQueue.new';
211
import {resetContextDependencies} from './ReactFiberNewContext.new';
@@ -2027,6 +2035,12 @@ function commitRootImpl(root, renderPriorityLevel) {
2035
legacyErrorBoundariesThatAlreadyFailed = null;
2036
}
2037
2038
+ if (__DEV__ && enableDoubleInvokingEffects) {
2039
+ if (!rootDidHavePassiveEffects) {
2040
+ commitDoubleInvokeEffectsInDEV(root.current, false);
2041
+ }
2042
+ }
2043
+
2044
if (enableSchedulerTracing) {
2045
if (!rootDidHavePassiveEffects) {
2046
// If there are no passive effects, then we can complete the pending interactions.
@@ -2590,15 +2604,6 @@ function flushPassiveEffectsImpl() {
2604
flushPassiveUnmountEffects(root.current);
2605
flushPassiveMountEffects(root, root.current);
2606
2593
- if (enableSchedulerTracing) {
2594
- popInteractions(((prevInteractions: any): Set<Interaction>));
2595
- finishPendingInteractions(root, lanes);
2596
- }
2597
-
2598
- if (__DEV__) {
2599
- isFlushingPassiveEffects = false;
2600
- }
2601
-
2607
if (__DEV__) {
2608
if (enableDebugTracing) {
2609
logPassiveEffectsStopped();
@@ -2609,6 +2614,19 @@ function flushPassiveEffectsImpl() {
2614
markPassiveEffectsStopped();
2615
}
2616
2617
+ if (__DEV__ && enableDoubleInvokingEffects) {
2618
+ commitDoubleInvokeEffectsInDEV(root.current, true);
2619
+ }
2620
+
2621
+ if (__DEV__) {
2622
+ isFlushingPassiveEffects = false;
2623
+ }
2624
+
2625
+ if (enableSchedulerTracing) {
2626
+ popInteractions(((prevInteractions: any): Set<Interaction>));
2627
+ finishPendingInteractions(root, lanes);
2628
+ }
2629
+
2630
executionContext = prevExecutionContext;
2631
2632
flushSyncCallbackQueue();
@@ -2886,6 +2904,52 @@ function flushRenderPhaseStrictModeWarningsInDEV() {
2904
}
2905
}
2906
2907
+function commitDoubleInvokeEffectsInDEV(
2908
+ fiber: Fiber,
2909
+ hasPassiveEffects: boolean,
2910
+) {
2911
+ if (__DEV__ && enableDoubleInvokingEffects) {
2912
+ setCurrentDebugFiberInDEV(fiber);
2913
+ invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
2914
+ if (hasPassiveEffects) {
2915
+ invokeEffectsInDev(
2916
+ fiber,
2917
+ MountPassiveDev,
2918
+ invokePassiveEffectUnmountInDEV,
2919
+ );
2920
+ }
2921
+
2922
+ invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectMountInDEV);
2923
+ if (hasPassiveEffects) {
2924
+ invokeEffectsInDev(fiber, MountPassiveDev, invokePassiveEffectMountInDEV);
2925
+ }
2926
+ resetCurrentDebugFiberInDEV();
2927
+ }
2928
+}
2929
+
2930
+function invokeEffectsInDev(
2931
+ firstChild: Fiber,
2932
+ fiberFlags: Flags,
2933
+ invokeEffectFn: (fiber: Fiber) => void,
2934
+): void {
2935
+ if (__DEV__ && enableDoubleInvokingEffects) {
2936
+ let fiber = firstChild;
2937
+ while (fiber !== null) {
2938
+ if (fiber.child !== null) {
2939
+ const primarySubtreeFlag = fiber.subtreeFlags & fiberFlags;
2940
+ if (primarySubtreeFlag !== NoFlags) {
2941
+ invokeEffectsInDev(fiber.child, fiberFlags, invokeEffectFn);
2942
+ }
2943
+ }
2944
+
2945
+ if ((fiber.flags & fiberFlags) !== NoFlags) {
2946
+ invokeEffectFn(fiber);
2947
+ }
2948
+ fiber = fiber.sibling;
2949
+ }
2950
+ }
2951
+}
2952
+
2953
let didWarnStateUpdateForNotYetMountedComponent: Set<string> | null = null;
2954
function warnAboutUpdateOnNotYetMountedFiberInDEV(fiber) {
2955
if (__DEV__) {
packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.internal.js
new
+504
@@ -0,0 +1,504 @@
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
+describe('ReactDoubleInvokeEvents', () => {
18
+ beforeEach(() => {
19
+ jest.resetModules();
20
+ React = require('react');
21
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
22
+ ReactNoop = require('react-noop-renderer');
23
+ Scheduler = require('scheduler');
24
+ ReactFeatureFlags.enableDoubleInvokingEffects = __VARIANT__;
25
+ });
26
+
27
+ it('double invoking for effects works properly', () => {
28
+ function App({text}) {
29
+ React.useEffect(() => {
30
+ Scheduler.unstable_yieldValue('useEffect mount');
31
+ return () => Scheduler.unstable_yieldValue('useEffect unmount');
32
+ });
33
+
34
+ React.useLayoutEffect(() => {
35
+ Scheduler.unstable_yieldValue('useLayoutEffect mount');
36
+ return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
37
+ });
38
+
39
+ return text;
40
+ }
41
+ ReactNoop.act(() => {
42
+ ReactNoop.render(<App text={'mount'} />);
43
+ });
44
+
45
+ if (__DEV__ && __VARIANT__) {
46
+ expect(Scheduler).toHaveYielded([
47
+ 'useLayoutEffect mount',
48
+ 'useEffect mount',
49
+ 'useLayoutEffect unmount',
50
+ 'useEffect unmount',
51
+ 'useLayoutEffect mount',
52
+ 'useEffect mount',
53
+ ]);
54
+ } else {
55
+ expect(Scheduler).toHaveYielded([
56
+ 'useLayoutEffect mount',
57
+ 'useEffect mount',
58
+ ]);
59
+ }
60
+
61
+ ReactNoop.act(() => {
62
+ ReactNoop.render(<App text={'update'} />);
63
+ });
64
+
65
+ expect(Scheduler).toHaveYielded([
66
+ 'useLayoutEffect unmount',
67
+ 'useLayoutEffect mount',
68
+ 'useEffect unmount',
69
+ 'useEffect mount',
70
+ ]);
71
+
72
+ ReactNoop.act(() => {
73
+ ReactNoop.render(null);
74
+ });
75
+
76
+ expect(Scheduler).toHaveYielded([
77
+ 'useLayoutEffect unmount',
78
+ 'useEffect unmount',
79
+ ]);
80
+ });
81
+
82
+ it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {
83
+ function App({text}) {
84
+ React.useEffect(() => {
85
+ Scheduler.unstable_yieldValue('useEffect One mount');
86
+ return () => Scheduler.unstable_yieldValue('useEffect One unmount');
87
+ });
88
+
89
+ React.useEffect(() => {
90
+ Scheduler.unstable_yieldValue('useEffect Two mount');
91
+ return () => Scheduler.unstable_yieldValue('useEffect Two unmount');
92
+ });
93
+
94
+ return text;
95
+ }
96
+
97
+ ReactNoop.act(() => {
98
+ ReactNoop.render(<App text={'mount'} />);
99
+ });
100
+
101
+ if (__DEV__ && __VARIANT__) {
102
+ expect(Scheduler).toHaveYielded([
103
+ 'useEffect One mount',
104
+ 'useEffect Two mount',
105
+ 'useEffect One unmount',
106
+ 'useEffect Two unmount',
107
+ 'useEffect One mount',
108
+ 'useEffect Two mount',
109
+ ]);
110
+ } else {
111
+ expect(Scheduler).toHaveYielded([
112
+ 'useEffect One mount',
113
+ 'useEffect Two mount',
114
+ ]);
115
+ }
116
+
117
+ ReactNoop.act(() => {
118
+ ReactNoop.render(<App text={'update'} />);
119
+ });
120
+
121
+ expect(Scheduler).toHaveYielded([
122
+ 'useEffect One unmount',
123
+ 'useEffect Two unmount',
124
+ 'useEffect One mount',
125
+ 'useEffect Two mount',
126
+ ]);
127
+
128
+ ReactNoop.act(() => {
129
+ ReactNoop.render(null);
130
+ });
131
+
132
+ expect(Scheduler).toHaveYielded([
133
+ 'useEffect One unmount',
134
+ 'useEffect Two unmount',
135
+ ]);
136
+ });
137
+
138
+ it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {
139
+ function App({text}) {
140
+ React.useLayoutEffect(() => {
141
+ Scheduler.unstable_yieldValue('useLayoutEffect One mount');
142
+ return () =>
143
+ Scheduler.unstable_yieldValue('useLayoutEffect One unmount');
144
+ });
145
+
146
+ React.useLayoutEffect(() => {
147
+ Scheduler.unstable_yieldValue('useLayoutEffect Two mount');
148
+ return () =>
149
+ Scheduler.unstable_yieldValue('useLayoutEffect Two unmount');
150
+ });
151
+
152
+ return text;
153
+ }
154
+
155
+ ReactNoop.act(() => {
156
+ ReactNoop.render(<App text={'mount'} />);
157
+ });
158
+
159
+ if (__DEV__ && __VARIANT__) {
160
+ expect(Scheduler).toHaveYielded([
161
+ 'useLayoutEffect One mount',
162
+ 'useLayoutEffect Two mount',
163
+ 'useLayoutEffect One unmount',
164
+ 'useLayoutEffect Two unmount',
165
+ 'useLayoutEffect One mount',
166
+ 'useLayoutEffect Two mount',
167
+ ]);
168
+ } else {
169
+ expect(Scheduler).toHaveYielded([
170
+ 'useLayoutEffect One mount',
171
+ 'useLayoutEffect Two mount',
172
+ ]);
173
+ }
174
+
175
+ ReactNoop.act(() => {
176
+ ReactNoop.render(<App text={'update'} />);
177
+ });
178
+
179
+ expect(Scheduler).toHaveYielded([
180
+ 'useLayoutEffect One unmount',
181
+ 'useLayoutEffect Two unmount',
182
+ 'useLayoutEffect One mount',
183
+ 'useLayoutEffect Two mount',
184
+ ]);
185
+
186
+ ReactNoop.act(() => {
187
+ ReactNoop.render(null);
188
+ });
189
+
190
+ expect(Scheduler).toHaveYielded([
191
+ 'useLayoutEffect One unmount',
192
+ 'useLayoutEffect Two unmount',
193
+ ]);
194
+ });
195
+
196
+ it('useEffect and useLayoutEffect is called twice when there is no unmount', () => {
197
+ function App({text}) {
198
+ React.useEffect(() => {
199
+ Scheduler.unstable_yieldValue('useEffect mount');
200
+ });
201
+
202
+ React.useLayoutEffect(() => {
203
+ Scheduler.unstable_yieldValue('useLayoutEffect mount');
204
+ });
205
+
206
+ return text;
207
+ }
208
+
209
+ ReactNoop.act(() => {
210
+ ReactNoop.render(<App text={'mount'} />);
211
+ });
212
+
213
+ if (__DEV__ && __VARIANT__) {
214
+ expect(Scheduler).toHaveYielded([
215
+ 'useLayoutEffect mount',
216
+ 'useEffect mount',
217
+ 'useLayoutEffect mount',
218
+ 'useEffect mount',
219
+ ]);
220
+ } else {
221
+ expect(Scheduler).toHaveYielded([
222
+ 'useLayoutEffect mount',
223
+ 'useEffect mount',
224
+ ]);
225
+ }
226
+
227
+ ReactNoop.act(() => {
228
+ ReactNoop.render(<App text={'update'} />);
229
+ });
230
+
231
+ expect(Scheduler).toHaveYielded([
232
+ 'useLayoutEffect mount',
233
+ 'useEffect mount',
234
+ ]);
235
+
236
+ ReactNoop.act(() => {
237
+ ReactNoop.render(null);
238
+ });
239
+
240
+ expect(Scheduler).toHaveYielded([]);
241
+ });
242
+
243
+ it('double invoking works for class components', () => {
244
+ class App extends React.PureComponent {
245
+ componentDidMount() {
246
+ Scheduler.unstable_yieldValue('componentDidMount');
247
+ }
248
+
249
+ componentDidUpdate() {
250
+ Scheduler.unstable_yieldValue('componentDidUpdate');
251
+ }
252
+
253
+ componentWillUnmount() {
254
+ Scheduler.unstable_yieldValue('componentWillUnmount');
255
+ }
256
+
257
+ render() {
258
+ return this.props.text;
259
+ }
260
+ }
261
+
262
+ ReactNoop.act(() => {
263
+ ReactNoop.render(<App text={'mount'} />);
264
+ });
265
+
266
+ if (__DEV__ && __VARIANT__) {
267
+ expect(Scheduler).toHaveYielded([
268
+ 'componentDidMount',
269
+ 'componentWillUnmount',
270
+ 'componentDidMount',
271
+ ]);
272
+ } else {
273
+ expect(Scheduler).toHaveYielded(['componentDidMount']);
274
+ }
275
+
276
+ ReactNoop.act(() => {
277
+ ReactNoop.render(<App text={'update'} />);
278
+ });
279
+
280
+ expect(Scheduler).toHaveYielded(['componentDidUpdate']);
281
+
282
+ ReactNoop.act(() => {
283
+ ReactNoop.render(null);
284
+ });
285
+
286
+ expect(Scheduler).toHaveYielded(['componentWillUnmount']);
287
+ });
288
+
289
+ it('double flushing passive effects only results in one double invoke', () => {
290
+ function App({text}) {
291
+ const [state, setState] = React.useState(0);
292
+ React.useEffect(() => {
293
+ if (state !== 1) {
294
+ setState(1);
295
+ }
296
+ Scheduler.unstable_yieldValue('useEffect mount');
297
+ return () => Scheduler.unstable_yieldValue('useEffect unmount');
298
+ });
299
+
300
+ React.useLayoutEffect(() => {
301
+ Scheduler.unstable_yieldValue('useLayoutEffect mount');
302
+ return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
303
+ });
304
+
305
+ Scheduler.unstable_yieldValue(text);
306
+ return text;
307
+ }
308
+
309
+ ReactNoop.act(() => {
310
+ ReactNoop.render(<App text={'mount'} />);
311
+ });
312
+
313
+ if (__DEV__ && __VARIANT__) {
314
+ expect(Scheduler).toHaveYielded([
315
+ 'mount',
316
+ 'useLayoutEffect mount',
317
+ 'useEffect mount',
318
+ 'useLayoutEffect unmount',
319
+ 'useEffect unmount',
320
+ 'useLayoutEffect mount',
321
+ 'useEffect mount',
322
+ 'mount',
323
+ 'useLayoutEffect unmount',
324
+ 'useLayoutEffect mount',
325
+ 'useEffect unmount',
326
+ 'useEffect mount',
327
+ ]);
328
+ } else {
329
+ expect(Scheduler).toHaveYielded([
330
+ 'mount',
331
+ 'useLayoutEffect mount',
332
+ 'useEffect mount',
333
+ 'mount',
334
+ 'useLayoutEffect unmount',
335
+ 'useLayoutEffect mount',
336
+ 'useEffect unmount',
337
+ 'useEffect mount',
338
+ ]);
339
+ }
340
+ });
341
+
342
+ it('newly mounted components after initial mount get double invoked', () => {
343
+ let _setShowChild;
344
+ function Child() {
345
+ React.useEffect(() => {
346
+ Scheduler.unstable_yieldValue('Child useEffect mount');
347
+ return () => Scheduler.unstable_yieldValue('Child useEffect unmount');
348
+ });
349
+ React.useLayoutEffect(() => {
350
+ Scheduler.unstable_yieldValue('Child useLayoutEffect mount');
351
+ return () =>
352
+ Scheduler.unstable_yieldValue('Child useLayoutEffect unmount');
353
+ });
354
+
355
+ return null;
356
+ }
357
+
358
+ function App() {
359
+ const [showChild, setShowChild] = React.useState(false);
360
+ _setShowChild = setShowChild;
361
+ React.useEffect(() => {
362
+ Scheduler.unstable_yieldValue('App useEffect mount');
363
+ return () => Scheduler.unstable_yieldValue('App useEffect unmount');
364
+ });
365
+ React.useLayoutEffect(() => {
366
+ Scheduler.unstable_yieldValue('App useLayoutEffect mount');
367
+ return () =>
368
+ Scheduler.unstable_yieldValue('App useLayoutEffect unmount');
369
+ });
370
+
371
+ return showChild && <Child />;
372
+ }
373
+
374
+ ReactNoop.act(() => {
375
+ ReactNoop.render(<App />);
376
+ });
377
+
378
+ if (__DEV__ && __VARIANT__) {
379
+ expect(Scheduler).toHaveYielded([
380
+ 'App useLayoutEffect mount',
381
+ 'App useEffect mount',
382
+ 'App useLayoutEffect unmount',
383
+ 'App useEffect unmount',
384
+ 'App useLayoutEffect mount',
385
+ 'App useEffect mount',
386
+ ]);
387
+ } else {
388
+ expect(Scheduler).toHaveYielded([
389
+ 'App useLayoutEffect mount',
390
+ 'App useEffect mount',
391
+ ]);
392
+ }
393
+
394
+ ReactNoop.act(() => {
395
+ _setShowChild(true);
396
+ });
397
+
398
+ if (__DEV__ && __VARIANT__) {
399
+ expect(Scheduler).toHaveYielded([
400
+ 'App useLayoutEffect unmount',
401
+ 'Child useLayoutEffect mount',
402
+ 'App useLayoutEffect mount',
403
+ 'App useEffect unmount',
404
+ 'Child useEffect mount',
405
+ 'App useEffect mount',
406
+ 'Child useLayoutEffect unmount',
407
+ 'Child useEffect unmount',
408
+ 'Child useLayoutEffect mount',
409
+ 'Child useEffect mount',
410
+ ]);
411
+ } else {
412
+ expect(Scheduler).toHaveYielded([
413
+ 'App useLayoutEffect unmount',
414
+ 'Child useLayoutEffect mount',
415
+ 'App useLayoutEffect mount',
416
+ 'App useEffect unmount',
417
+ 'Child useEffect mount',
418
+ 'App useEffect mount',
419
+ ]);
420
+ }
421
+ });
422
+
423
+ it('classes and functions are double invoked together correctly', () => {
424
+ class ClassChild extends React.PureComponent {
425
+ componentDidMount() {
426
+ Scheduler.unstable_yieldValue('componentDidMount');
427
+ }
428
+
429
+ componentWillUnmount() {
430
+ Scheduler.unstable_yieldValue('componentWillUnmount');
431
+ }
432
+
433
+ render() {
434
+ return this.props.text;
435
+ }
436
+ }
437
+
438
+ function FunctionChild({text}) {
439
+ React.useEffect(() => {
440
+ Scheduler.unstable_yieldValue('useEffect mount');
441
+ return () => Scheduler.unstable_yieldValue('useEffect unmount');
442
+ });
443
+ React.useLayoutEffect(() => {
444
+ Scheduler.unstable_yieldValue('useLayoutEffect mount');
445
+ return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
446
+ });
447
+ return text;
448
+ }
449
+
450
+ function App({text}) {
451
+ return (
452
+ <>
453
+ <ClassChild text={text} />
454
+ <FunctionChild text={text} />
455
+ </>
456
+ );
457
+ }
458
+
459
+ ReactNoop.act(() => {
460
+ ReactNoop.render(<App text={'mount'} />);
461
+ });
462
+
463
+ if (__DEV__ && __VARIANT__) {
464
+ expect(Scheduler).toHaveYielded([
465
+ 'componentDidMount',
466
+ 'useLayoutEffect mount',
467
+ 'useEffect mount',
468
+ 'componentWillUnmount',
469
+ 'useLayoutEffect unmount',
470
+ 'useEffect unmount',
471
+ 'componentDidMount',
472
+ 'useLayoutEffect mount',
473
+ 'useEffect mount',
474
+ ]);
475
+ } else {
476
+ expect(Scheduler).toHaveYielded([
477
+ 'componentDidMount',
478
+ 'useLayoutEffect mount',
479
+ 'useEffect mount',
480
+ ]);
481
+ }
482
+
483
+ ReactNoop.act(() => {
484
+ ReactNoop.render(<App text={'mount'} />);
485
+ });
486
+
487
+ expect(Scheduler).toHaveYielded([
488
+ 'useLayoutEffect unmount',
489
+ 'useLayoutEffect mount',
490
+ 'useEffect unmount',
491
+ 'useEffect mount',
492
+ ]);
493
+
494
+ ReactNoop.act(() => {
495
+ ReactNoop.render(null);
496
+ });
497
+
498
+ expect(Scheduler).toHaveYielded([
499
+ 'componentWillUnmount',
500
+ 'useLayoutEffect unmount',
501
+ 'useEffect unmount',
502
+ ]);
503
+ });
504
+});
packages/react/src/__tests__/ReactProfiler-test.internal.js
+43
@@ -4149,6 +4149,49 @@ describe('Profiler', () => {
4149
expect(onRender.mock.calls[2][2]).toBe(15); // actual
4150
expect(onRender.mock.calls[2][3]).toBe(1 + 15); // base
4151
});
4152
+
4153
+ if (__DEV__) {
4154
+ // @gate new
4155
+ it('double invoking does not disconnect wrapped async work', () => {
4156
+ ReactFeatureFlags.enableDoubleInvokingEffects = true;
4157
+
4158
+ const callback = jest.fn(() => {
4159
+ const wrappedInteractions = SchedulerTracing.unstable_getCurrent();
4160
+ // Expect wrappedInteractions and interactions to be the same set.
4161
+ expect(wrappedInteractions).toMatchInteractions([interaction]);
4162
+ });
4163
+
4164
+ const Component = jest.fn(() => {
4165
+ React.useEffect(() => {
4166
+ setTimeout(SchedulerTracing.unstable_wrap(callback), 0);
4167
+ });
4168
+ React.useLayoutEffect(() => {
4169
+ setTimeout(SchedulerTracing.unstable_wrap(callback), 0);
4170
+ });
4171
+
4172
+ return null;
4173
+ });
4174
+
4175
+ let interaction;
4176
+ SchedulerTracing.unstable_trace(
4177
+ 'event',
4178
+ Scheduler.unstable_now(),
4179
+ () => {
4180
+ const interactions = SchedulerTracing.unstable_getCurrent();
4181
+ expect(interactions.size).toBe(1);
4182
+ interaction = Array.from(interactions)[0];
4183
+ ReactTestRenderer.create(<Component />);
4184
+ },
4185
+ );
4186
+ Scheduler.unstable_flushAll();
4187
+
4188
+ jest.runAllTimers();
4189
+
4190
+ expect(callback).toHaveBeenCalledTimes(4); // 2x per effect
4191
+
4192
+ expect(onInteractionScheduledWorkCompleted).toHaveBeenCalledTimes(1);
4193
+ });
4194
+ }
4195
});
4196
});
4197
});
packages/shared/ReactFeatureFlags.js
+2
@@ -136,3 +136,5 @@ export const enableDiscreteEventFlushingChange = false;
136
export const enableEagerRootListeners = true;
137
138
export const disableSchedulerTimeoutInWorkLoop = false;
139
+
140
+export const enableDoubleInvokingEffects = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+2
@@ -52,6 +52,8 @@ export const enableDiscreteEventFlushingChange = false;
52
export const enableEagerRootListeners = true;
53
export const disableSchedulerTimeoutInWorkLoop = false;
54
55
+export const enableDoubleInvokingEffects = false;
56
+
57
// Flow magic to verify the exports of this file match the original version.
58
// eslint-disable-next-line no-unused-vars
59
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+2
@@ -51,6 +51,8 @@ export const enableDiscreteEventFlushingChange = false;
51
export const enableEagerRootListeners = true;
52
export const disableSchedulerTimeoutInWorkLoop = false;
53
54
+export const enableDoubleInvokingEffects = false;
55
+
56
// Flow magic to verify the exports of this file match the original version.
57
// eslint-disable-next-line no-unused-vars
58
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+2
@@ -51,6 +51,8 @@ export const enableDiscreteEventFlushingChange = false;
51
export const enableEagerRootListeners = true;
52
export const disableSchedulerTimeoutInWorkLoop = false;
53
54
+export const enableDoubleInvokingEffects = false;
55
+
56
// Flow magic to verify the exports of this file match the original version.
57
// eslint-disable-next-line no-unused-vars
58
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+2
@@ -51,6 +51,8 @@ export const enableDiscreteEventFlushingChange = false;
51
export const enableEagerRootListeners = true;
52
export const disableSchedulerTimeoutInWorkLoop = false;
53
54
+export const enableDoubleInvokingEffects = false;
55
+
56
// Flow magic to verify the exports of this file match the original version.
57
// eslint-disable-next-line no-unused-vars
58
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+2
@@ -51,6 +51,8 @@ export const enableDiscreteEventFlushingChange = false;
51
export const enableEagerRootListeners = true;
52
export const disableSchedulerTimeoutInWorkLoop = false;
53
54
+export const enableDoubleInvokingEffects = false;
55
+
56
// Flow magic to verify the exports of this file match the original version.
57
// eslint-disable-next-line no-unused-vars
58
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.testing.js
+2
@@ -51,6 +51,8 @@ export const enableDiscreteEventFlushingChange = false;
51
export const enableEagerRootListeners = true;
52
export const disableSchedulerTimeoutInWorkLoop = false;
53
54
+export const enableDoubleInvokingEffects = false;
55
+
56
// Flow magic to verify the exports of this file match the original version.
57
// eslint-disable-next-line no-unused-vars
58
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.testing.www.js
+2
@@ -51,6 +51,8 @@ export const enableDiscreteEventFlushingChange = true;
51
export const enableEagerRootListeners = true;
52
export const disableSchedulerTimeoutInWorkLoop = false;
53
54
+export const enableDoubleInvokingEffects = false;
55
+
56
// Flow magic to verify the exports of this file match the original version.
57
// eslint-disable-next-line no-unused-vars
58
type Check<_X, Y: _X, X: Y = _X> = null;
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+2
@@ -48,3 +48,5 @@ export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
48
export const enableTrustedTypesIntegration = false;
49
export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
50
export const disableSchedulerTimeoutInWorkLoop = __VARIANT__;
51
+
52
+export const enableDoubleInvokingEffects = false;
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -29,6 +29,7 @@ export const {
29
skipUnmountedBoundaries,
30
enableEagerRootListeners,
31
disableSchedulerTimeoutInWorkLoop,
32
+ enableDoubleInvokingEffects,
33
} = dynamicFeatureFlags;
34
35
// On WWW, __EXPERIMENTAL__ is used for a new modern build.