@samitouri / QOS-React-2 / commits / 9209c30ff9

Add StrictMode level prop and createRoot unstable_strictModeLevel option (#20849)

* The exported '<React.StrictMode>' tag remains the same and opts legacy subtrees into strict mode level one ('mode == StrictModeL1'). This mode enables DEV-only double rendering, double component lifecycles, string ref warnings, legacy context warnings, etc. The primary purpose of this mode is to help detected render phase side effects. No new behavior. Roots created with experimental 'createRoot' and 'createBlockingRoot' APIs will also (for now) continue to default to strict mode level 1. In a subsequent commit I will add support for a 'level' attribute on the '<React.StrictMode>' tag (as well as a new option supported by ). This will be the way to opt into strict mode level 2 ('mode == StrictModeL2'). This mode will enable DEV-only double invoking of effects on initial mount. This will simulate future Offscreen API semantics for trees being mounted, then hidden, and then shown again. The primary purpose of this mode is to enable applications to prepare for compatibility with the new Offscreen API (more information to follow shortly). For now, this commit changes no public facing behavior. The only mechanism for opting into strict mode level 2 is the pre-existing 'enableDoubleInvokingEffects' feature flag (only enabled within Facebook for now). * Renamed strict mode constants StrictModeL1 -> StrictLegacyMode and StrictModeL2 -> StrictEffectsMode * Renamed tests * Split strict effects mode into two flags One flag ('enableStrictEffects') enables strict mode level 2. It is similar to 'debugRenderPhaseSideEffectsForStrictMode' which enables srtict mode level 1. The second flag ('createRootStrictEffectsByDefault') controls the default strict mode level for 'createRoot' trees. For now, all 'createRoot' trees remain level 1 by default. We will experiment with level 2 within Facebook. This is a prerequisite for adding a configurable option to 'createRoot' that enables choosing a different StrictMode level than the default. * Add StrictMode 'unstable_level' prop and createRoot 'unstable_strictModeLevel' option New StrictMode 'unstable_level' prop allows specifying which level of strict mode to use. If no level attribute is specified, StrictLegacyMode will be used to maintain backwards compatibility. Otherwise the following is true: * Level 0 does nothing * Level 1 selects StrictLegacyMode * Level 2 selects StrictEffectsMode (which includes StrictLegacyMode) Levels can be increased with nesting (0 -> 1 -> 2) but not decreased. This commit also adds a new 'unstable_strictModeLevel' option to the createRoot and createBatchedRoot APIs. This option can be used to override default behavior to increase or decrease the StrictMode level of the root. A subsequent commit will add additional DEV warnings: * If a nested StrictMode tag attempts to explicitly decrease the level * If a level attribute changes in an update

Brian Vaughn committed Feb 24, 2021 at 16:14 UTC 9209c30ff98528cfbbe9df0774fda0b117975a25
43 files changed +1243 -934
packages/react-dom/src/client/ReactDOMRoot.js
+13 -1
@@ -27,6 +27,7 @@ export type RootOptions = {
27 mutableSources?: Array<MutableSource<any>>,
28 ...
29 },
30 + unstable_strictModeLevel?: number,
31 ...
32 };
33
@@ -128,7 +129,18 @@ function createRootImpl(
129 options.hydrationOptions != null &&
130 options.hydrationOptions.mutableSources) ||
131 null;
131 - const root = createContainer(container, tag, hydrate, hydrationCallbacks);
132 + const strictModeLevelOverride =
133 + options != null && options.unstable_strictModeLevel != null
134 + ? options.unstable_strictModeLevel
135 + : null;
136 +
137 + const root = createContainer(
138 + container,
139 + tag,
140 + hydrate,
141 + hydrationCallbacks,
142 + strictModeLevelOverride,
143 + );
144 markContainerAsRoot(root.current, container);
145
146 const rootContainerElement =
packages/react-native-renderer/src/ReactFabric.js
+1 -1
@@ -203,7 +203,7 @@ function render(
203 if (!root) {
204 // TODO (bvaughn): If we decide to keep the wrapper component,
205 // We could create a wrapper for containerTag as well to reduce special casing.
206 - root = createContainer(containerTag, LegacyRoot, false, null);
206 + root = createContainer(containerTag, LegacyRoot, false, null, null);
207 roots.set(containerTag, root);
208 }
209 updateContainer(element, root, null, callback);
packages/react-native-renderer/src/ReactNativeRenderer.js
+1 -1
@@ -202,7 +202,7 @@ function render(
202 if (!root) {
203 // TODO (bvaughn): If we decide to keep the wrapper component,
204 // We could create a wrapper for containerTag as well to reduce special casing.
205 - root = createContainer(containerTag, LegacyRoot, false, null);
205 + root = createContainer(containerTag, LegacyRoot, false, null, null);
206 roots.set(containerTag, root);
207 }
208 updateContainer(element, root, null, callback);
packages/react-noop-renderer/src/createReactNoop.js
+4 -1
@@ -722,7 +722,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
722 if (!root) {
723 const container = {rootID: rootID, pendingChildren: [], children: []};
724 rootContainers.set(rootID, container);
725 - root = NoopRenderer.createContainer(container, tag, false, null);
725 + root = NoopRenderer.createContainer(container, tag, false, null, null);
726 roots.set(rootID, root);
727 }
728 return root.current.stateNode.containerInfo;
@@ -740,6 +740,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
740 ConcurrentRoot,
741 false,
742 null,
743 + null,
744 );
745 return {
746 _Scheduler: Scheduler,
@@ -766,6 +767,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
767 BlockingRoot,
768 false,
769 null,
770 + null,
771 );
772 return {
773 _Scheduler: Scheduler,
@@ -792,6 +794,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
794 LegacyRoot,
795 false,
796 null,
797 + null,
798 );
799 return {
800 _Scheduler: Scheduler,
packages/react-reconciler/src/ReactChildFiber.new.js
+2 -2
@@ -46,7 +46,7 @@ import {
46 } from './ReactFiber.new';
47 import {emptyRefsObject} from './ReactFiberClassComponent.new';
48 import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.new';
49 -import {StrictMode} from './ReactTypeOfMode';
49 +import {StrictLegacyMode} from './ReactTypeOfMode';
50
51 let didWarnAboutMaps;
52 let didWarnAboutGenerators;
@@ -114,7 +114,7 @@ function coerceRef(
114 // TODO: Clean this up once we turn on the string ref warning for
115 // everyone, because the strict mode case will no longer be relevant
116 if (
117 - (returnFiber.mode & StrictMode || warnAboutStringRefs) &&
117 + (returnFiber.mode & StrictLegacyMode || warnAboutStringRefs) &&
118 // We warn in ReactElement.js if owner and self are equal for string refs
119 // because these cannot be automatically converted to an arrow function
120 // using a codemod. Therefore, we don't have to warn about string refs again.
packages/react-reconciler/src/ReactChildFiber.old.js
+2 -2
@@ -46,7 +46,7 @@ import {
46 } from './ReactFiber.old';
47 import {emptyRefsObject} from './ReactFiberClassComponent.old';
48 import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.old';
49 -import {StrictMode} from './ReactTypeOfMode';
49 +import {StrictLegacyMode} from './ReactTypeOfMode';
50
51 let didWarnAboutMaps;
52 let didWarnAboutGenerators;
@@ -114,7 +114,7 @@ function coerceRef(
114 // TODO: Clean this up once we turn on the string ref warning for
115 // everyone, because the strict mode case will no longer be relevant
116 if (
117 - (returnFiber.mode & StrictMode || warnAboutStringRefs) &&
117 + (returnFiber.mode & StrictLegacyMode || warnAboutStringRefs) &&
118 // We warn in ReactElement.js if owner and self are equal for string refs
119 // because these cannot be automatically converted to an arrow function
120 // using a codemod. Therefore, we don't have to warn about string refs again.
packages/react-reconciler/src/ReactFiber.new.js
+58 -6
@@ -19,9 +19,11 @@ import type {OffscreenProps} from './ReactFiberOffscreenComponent';
19
20 import invariant from 'shared/invariant';
21 import {
22 + createRootStrictEffectsByDefault,
23 + enableCache,
24 + enableStrictEffects,
25 enableProfilerTimer,
26 enableScopeAPI,
24 - enableCache,
27 } from 'shared/ReactFeatureFlags';
28 import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
29 import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
@@ -64,7 +66,8 @@ import {
66 ConcurrentMode,
67 DebugTracingMode,
68 ProfileMode,
67 - StrictMode,
69 + StrictLegacyMode,
70 + StrictEffectsMode,
71 BlockingMode,
72 } from './ReactTypeOfMode';
73 import {
@@ -418,12 +421,47 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
421 return workInProgress;
422 }
423
421 -export function createHostRootFiber(tag: RootTag): Fiber {
424 +export function createHostRootFiber(
425 + tag: RootTag,
426 + strictModeLevelOverride: null | number,
427 +): Fiber {
428 let mode;
429 if (tag === ConcurrentRoot) {
424 - mode = ConcurrentMode | BlockingMode | StrictMode;
430 + mode = ConcurrentMode | BlockingMode;
431 + if (strictModeLevelOverride !== null) {
432 + if (strictModeLevelOverride >= 1) {
433 + mode |= StrictLegacyMode;
434 + }
435 + if (enableStrictEffects) {
436 + if (strictModeLevelOverride >= 2) {
437 + mode |= StrictEffectsMode;
438 + }
439 + }
440 + } else {
441 + if (enableStrictEffects && createRootStrictEffectsByDefault) {
442 + mode |= StrictLegacyMode | StrictEffectsMode;
443 + } else {
444 + mode |= StrictLegacyMode;
445 + }
446 + }
447 } else if (tag === BlockingRoot) {
426 - mode = BlockingMode | StrictMode;
448 + mode = BlockingMode;
449 + if (strictModeLevelOverride !== null) {
450 + if (strictModeLevelOverride >= 1) {
451 + mode |= StrictLegacyMode;
452 + }
453 + if (enableStrictEffects) {
454 + if (strictModeLevelOverride >= 2) {
455 + mode |= StrictEffectsMode;
456 + }
457 + }
458 + } else {
459 + if (enableStrictEffects && createRootStrictEffectsByDefault) {
460 + mode |= StrictLegacyMode | StrictEffectsMode;
461 + } else {
462 + mode |= StrictLegacyMode;
463 + }
464 + }
465 } else {
466 mode = NoMode;
467 }
@@ -472,7 +510,21 @@ export function createFiberFromTypeAndProps(
510 break;
511 case REACT_STRICT_MODE_TYPE:
512 fiberTag = Mode;
475 - mode |= StrictMode;
513 +
514 + // Legacy strict mode (<StrictMode> without any level prop) defaults to level 1.
515 + const level =
516 + pendingProps.unstable_level == null ? 1 : pendingProps.unstable_level;
517 +
518 + // Levels cascade; higher levels inherit all lower level modes.
519 + // It is explicitly not supported to lower a mode with nesting, only to increase it.
520 + if (level >= 1) {
521 + mode |= StrictLegacyMode;
522 + }
523 + if (enableStrictEffects) {
524 + if (level >= 2) {
525 + mode |= StrictEffectsMode;
526 + }
527 + }
528 break;
529 case REACT_PROFILER_TYPE:
530 return createFiberFromProfiler(pendingProps, mode, lanes, key);
packages/react-reconciler/src/ReactFiber.old.js
+58 -6
@@ -19,9 +19,11 @@ import type {OffscreenProps} from './ReactFiberOffscreenComponent';
19
20 import invariant from 'shared/invariant';
21 import {
22 + createRootStrictEffectsByDefault,
23 + enableCache,
24 + enableStrictEffects,
25 enableProfilerTimer,
26 enableScopeAPI,
24 - enableCache,
27 } from 'shared/ReactFeatureFlags';
28 import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
29 import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
@@ -64,7 +66,8 @@ import {
66 ConcurrentMode,
67 DebugTracingMode,
68 ProfileMode,
67 - StrictMode,
69 + StrictLegacyMode,
70 + StrictEffectsMode,
71 BlockingMode,
72 } from './ReactTypeOfMode';
73 import {
@@ -418,12 +421,47 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
421 return workInProgress;
422 }
423
421 -export function createHostRootFiber(tag: RootTag): Fiber {
424 +export function createHostRootFiber(
425 + tag: RootTag,
426 + strictModeLevelOverride: null | number,
427 +): Fiber {
428 let mode;
429 if (tag === ConcurrentRoot) {
424 - mode = ConcurrentMode | BlockingMode | StrictMode;
430 + mode = ConcurrentMode | BlockingMode;
431 + if (strictModeLevelOverride !== null) {
432 + if (strictModeLevelOverride >= 1) {
433 + mode |= StrictLegacyMode;
434 + }
435 + if (enableStrictEffects) {
436 + if (strictModeLevelOverride >= 2) {
437 + mode |= StrictEffectsMode;
438 + }
439 + }
440 + } else {
441 + if (enableStrictEffects && createRootStrictEffectsByDefault) {
442 + mode |= StrictLegacyMode | StrictEffectsMode;
443 + } else {
444 + mode |= StrictLegacyMode;
445 + }
446 + }
447 } else if (tag === BlockingRoot) {
426 - mode = BlockingMode | StrictMode;
448 + mode = BlockingMode;
449 + if (strictModeLevelOverride !== null) {
450 + if (strictModeLevelOverride >= 1) {
451 + mode |= StrictLegacyMode;
452 + }
453 + if (enableStrictEffects) {
454 + if (strictModeLevelOverride >= 2) {
455 + mode |= StrictEffectsMode;
456 + }
457 + }
458 + } else {
459 + if (enableStrictEffects && createRootStrictEffectsByDefault) {
460 + mode |= StrictLegacyMode | StrictEffectsMode;
461 + } else {
462 + mode |= StrictLegacyMode;
463 + }
464 + }
465 } else {
466 mode = NoMode;
467 }
@@ -472,7 +510,21 @@ export function createFiberFromTypeAndProps(
510 break;
511 case REACT_STRICT_MODE_TYPE:
512 fiberTag = Mode;
475 - mode |= StrictMode;
513 +
514 + // Legacy strict mode (<StrictMode> without any level prop) defaults to level 1.
515 + const level =
516 + pendingProps.unstable_level == null ? 1 : pendingProps.unstable_level;
517 +
518 + // Levels cascade; higher levels inherit all lower level modes.
519 + // It is explicitly not supported to lower a mode with nesting, only to increase it.
520 + if (level >= 1) {
521 + mode |= StrictLegacyMode;
522 + }
523 + if (enableStrictEffects) {
524 + if (level >= 2) {
525 + mode |= StrictEffectsMode;
526 + }
527 + }
528 break;
529 case REACT_PROFILER_TYPE:
530 return createFiberFromProfiler(pendingProps, mode, lanes, key);
packages/react-reconciler/src/ReactFiberBeginWork.new.js
+6 -6
@@ -125,7 +125,7 @@ import {
125 ConcurrentMode,
126 NoMode,
127 ProfileMode,
128 - StrictMode,
128 + StrictLegacyMode,
129 BlockingMode,
130 } from './ReactTypeOfMode';
131 import {
@@ -357,7 +357,7 @@ function updateForwardRef(
357 );
358 if (
359 debugRenderPhaseSideEffectsForStrictMode &&
360 - workInProgress.mode & StrictMode
360 + workInProgress.mode & StrictLegacyMode
361 ) {
362 disableLogs();
363 try {
@@ -889,7 +889,7 @@ function updateFunctionComponent(
889 );
890 if (
891 debugRenderPhaseSideEffectsForStrictMode &&
892 - workInProgress.mode & StrictMode
892 + workInProgress.mode & StrictLegacyMode
893 ) {
894 disableLogs();
895 try {
@@ -1068,7 +1068,7 @@ function finishClassComponent(
1068 nextChildren = instance.render();
1069 if (
1070 debugRenderPhaseSideEffectsForStrictMode &&
1071 - workInProgress.mode & StrictMode
1071 + workInProgress.mode & StrictLegacyMode
1072 ) {
1073 disableLogs();
1074 try {
@@ -1478,7 +1478,7 @@ function mountIndeterminateComponent(
1478 }
1479 }
1480
1481 - if (workInProgress.mode & StrictMode) {
1481 + if (workInProgress.mode & StrictLegacyMode) {
1482 ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
1483 }
1484
@@ -1615,7 +1615,7 @@ function mountIndeterminateComponent(
1615
1616 if (
1617 debugRenderPhaseSideEffectsForStrictMode &&
1618 - workInProgress.mode & StrictMode
1618 + workInProgress.mode & StrictLegacyMode
1619 ) {
1620 disableLogs();
1621 try {
packages/react-reconciler/src/ReactFiberBeginWork.old.js
+6 -6
@@ -125,7 +125,7 @@ import {
125 ConcurrentMode,
126 NoMode,
127 ProfileMode,
128 - StrictMode,
128 + StrictLegacyMode,
129 BlockingMode,
130 } from './ReactTypeOfMode';
131 import {
@@ -357,7 +357,7 @@ function updateForwardRef(
357 );
358 if (
359 debugRenderPhaseSideEffectsForStrictMode &&
360 - workInProgress.mode & StrictMode
360 + workInProgress.mode & StrictLegacyMode
361 ) {
362 disableLogs();
363 try {
@@ -889,7 +889,7 @@ function updateFunctionComponent(
889 );
890 if (
891 debugRenderPhaseSideEffectsForStrictMode &&
892 - workInProgress.mode & StrictMode
892 + workInProgress.mode & StrictLegacyMode
893 ) {
894 disableLogs();
895 try {
@@ -1068,7 +1068,7 @@ function finishClassComponent(
1068 nextChildren = instance.render();
1069 if (
1070 debugRenderPhaseSideEffectsForStrictMode &&
1071 - workInProgress.mode & StrictMode
1071 + workInProgress.mode & StrictLegacyMode
1072 ) {
1073 disableLogs();
1074 try {
@@ -1478,7 +1478,7 @@ function mountIndeterminateComponent(
1478 }
1479 }
1480
1481 - if (workInProgress.mode & StrictMode) {
1481 + if (workInProgress.mode & StrictLegacyMode) {
1482 ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
1483 }
1484
@@ -1615,7 +1615,7 @@ function mountIndeterminateComponent(
1615
1616 if (
1617 debugRenderPhaseSideEffectsForStrictMode &&
1618 - workInProgress.mode & StrictMode
1618 + workInProgress.mode & StrictLegacyMode
1619 ) {
1620 disableLogs();
1621 try {
packages/react-reconciler/src/ReactFiberClassComponent.new.js
+15 -16
@@ -19,7 +19,7 @@ import {
19 enableDebugTracing,
20 enableSchedulingProfiler,
21 warnAboutDeprecatedLifecycles,
22 - enableDoubleInvokingEffects,
22 + enableStrictEffects,
23 } from 'shared/ReactFeatureFlags';
24 import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
25 import {isMounted} from './ReactFiberTreeReflection';
@@ -31,11 +31,10 @@ import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
31
32 import {resolveDefaultProps} from './ReactFiberLazyComponent.new';
33 import {
34 - BlockingMode,
35 - ConcurrentMode,
34 DebugTracingMode,
35 NoMode,
38 - StrictMode,
36 + StrictLegacyMode,
37 + StrictEffectsMode,
38 } from './ReactTypeOfMode';
39
40 import {
@@ -165,7 +164,7 @@ export function applyDerivedStateFromProps(
164 if (__DEV__) {
165 if (
166 debugRenderPhaseSideEffectsForStrictMode &&
168 - workInProgress.mode & StrictMode
167 + workInProgress.mode & StrictLegacyMode
168 ) {
169 disableLogs();
170 try {
@@ -318,7 +317,7 @@ function checkShouldComponentUpdate(
317 if (__DEV__) {
318 if (
319 debugRenderPhaseSideEffectsForStrictMode &&
321 - workInProgress.mode & StrictMode
320 + workInProgress.mode & StrictLegacyMode
321 ) {
322 disableLogs();
323 try {
@@ -655,7 +654,7 @@ function constructClassInstance(
654 if (__DEV__) {
655 if (
656 debugRenderPhaseSideEffectsForStrictMode &&
658 - workInProgress.mode & StrictMode
657 + workInProgress.mode & StrictLegacyMode
658 ) {
659 disableLogs();
660 try {
@@ -862,7 +861,7 @@ function mountClassInstance(
861 }
862 }
863
865 - if (workInProgress.mode & StrictMode) {
864 + if (workInProgress.mode & StrictLegacyMode) {
865 ReactStrictModeWarnings.recordLegacyContextWarning(
866 workInProgress,
867 instance,
@@ -909,8 +908,8 @@ function mountClassInstance(
908 if (typeof instance.componentDidMount === 'function') {
909 if (
910 __DEV__ &&
912 - enableDoubleInvokingEffects &&
913 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
911 + enableStrictEffects &&
912 + (workInProgress.mode & StrictEffectsMode) !== NoMode
913 ) {
914 // Never double-invoke effects for legacy roots.
915 workInProgress.flags |= MountLayoutDev | Update;
@@ -988,8 +987,8 @@ function resumeMountClassInstance(
987 if (typeof instance.componentDidMount === 'function') {
988 if (
989 __DEV__ &&
991 - enableDoubleInvokingEffects &&
992 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
990 + enableStrictEffects &&
991 + (workInProgress.mode & StrictEffectsMode) !== NoMode
992 ) {
993 // Never double-invoke effects for legacy roots.
994 workInProgress.flags |= MountLayoutDev | Update;
@@ -1040,8 +1039,8 @@ function resumeMountClassInstance(
1039 if (typeof instance.componentDidMount === 'function') {
1040 if (
1041 __DEV__ &&
1043 - enableDoubleInvokingEffects &&
1044 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1042 + enableStrictEffects &&
1043 + (workInProgress.mode & StrictEffectsMode) !== NoMode
1044 ) {
1045 // Never double-invoke effects for legacy roots.
1046 workInProgress.flags |= MountLayoutDev | Update;
@@ -1055,8 +1054,8 @@ function resumeMountClassInstance(
1054 if (typeof instance.componentDidMount === 'function') {
1055 if (
1056 __DEV__ &&
1058 - enableDoubleInvokingEffects &&
1059 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1057 + enableStrictEffects &&
1058 + (workInProgress.mode & StrictEffectsMode) !== NoMode
1059 ) {
1060 // Never double-invoke effects for legacy roots.
1061 workInProgress.flags |= MountLayoutDev | Update;
packages/react-reconciler/src/ReactFiberClassComponent.old.js
+15 -16
@@ -19,7 +19,7 @@ import {
19 enableDebugTracing,
20 enableSchedulingProfiler,
21 warnAboutDeprecatedLifecycles,
22 - enableDoubleInvokingEffects,
22 + enableStrictEffects,
23 } from 'shared/ReactFeatureFlags';
24 import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
25 import {isMounted} from './ReactFiberTreeReflection';
@@ -31,11 +31,10 @@ import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols';
31
32 import {resolveDefaultProps} from './ReactFiberLazyComponent.old';
33 import {
34 - BlockingMode,
35 - ConcurrentMode,
34 DebugTracingMode,
35 NoMode,
38 - StrictMode,
36 + StrictLegacyMode,
37 + StrictEffectsMode,
38 } from './ReactTypeOfMode';
39
40 import {
@@ -165,7 +164,7 @@ export function applyDerivedStateFromProps(
164 if (__DEV__) {
165 if (
166 debugRenderPhaseSideEffectsForStrictMode &&
168 - workInProgress.mode & StrictMode
167 + workInProgress.mode & StrictLegacyMode
168 ) {
169 disableLogs();
170 try {
@@ -318,7 +317,7 @@ function checkShouldComponentUpdate(
317 if (__DEV__) {
318 if (
319 debugRenderPhaseSideEffectsForStrictMode &&
321 - workInProgress.mode & StrictMode
320 + workInProgress.mode & StrictLegacyMode
321 ) {
322 disableLogs();
323 try {
@@ -655,7 +654,7 @@ function constructClassInstance(
654 if (__DEV__) {
655 if (
656 debugRenderPhaseSideEffectsForStrictMode &&
658 - workInProgress.mode & StrictMode
657 + workInProgress.mode & StrictLegacyMode
658 ) {
659 disableLogs();
660 try {
@@ -862,7 +861,7 @@ function mountClassInstance(
861 }
862 }
863
865 - if (workInProgress.mode & StrictMode) {
864 + if (workInProgress.mode & StrictLegacyMode) {
865 ReactStrictModeWarnings.recordLegacyContextWarning(
866 workInProgress,
867 instance,
@@ -909,8 +908,8 @@ function mountClassInstance(
908 if (typeof instance.componentDidMount === 'function') {
909 if (
910 __DEV__ &&
912 - enableDoubleInvokingEffects &&
913 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
911 + enableStrictEffects &&
912 + (workInProgress.mode & StrictEffectsMode) !== NoMode
913 ) {
914 // Never double-invoke effects for legacy roots.
915 workInProgress.flags |= MountLayoutDev | Update;
@@ -988,8 +987,8 @@ function resumeMountClassInstance(
987 if (typeof instance.componentDidMount === 'function') {
988 if (
989 __DEV__ &&
991 - enableDoubleInvokingEffects &&
992 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
990 + enableStrictEffects &&
991 + (workInProgress.mode & StrictEffectsMode) !== NoMode
992 ) {
993 // Never double-invoke effects for legacy roots.
994 workInProgress.flags |= MountLayoutDev | Update;
@@ -1040,8 +1039,8 @@ function resumeMountClassInstance(
1039 if (typeof instance.componentDidMount === 'function') {
1040 if (
1041 __DEV__ &&
1043 - enableDoubleInvokingEffects &&
1044 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1042 + enableStrictEffects &&
1043 + (workInProgress.mode & StrictEffectsMode) !== NoMode
1044 ) {
1045 // Never double-invoke effects for legacy roots.
1046 workInProgress.flags |= MountLayoutDev | Update;
@@ -1055,8 +1054,8 @@ function resumeMountClassInstance(
1054 if (typeof instance.componentDidMount === 'function') {
1055 if (
1056 __DEV__ &&
1058 - enableDoubleInvokingEffects &&
1059 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1057 + enableStrictEffects &&
1058 + (workInProgress.mode & StrictEffectsMode) !== NoMode
1059 ) {
1060 // Never double-invoke effects for legacy roots.
1061 workInProgress.flags |= MountLayoutDev | Update;
packages/react-reconciler/src/ReactFiberCommitWork.new.js
+13 -13
@@ -35,7 +35,7 @@ import {
35 enableSuspenseServerRenderer,
36 enableSuspenseCallback,
37 enableScopeAPI,
38 - enableDoubleInvokingEffects,
38 + enableStrictEffects,
39 } from 'shared/ReactFeatureFlags';
40 import {
41 FunctionComponent,
@@ -2475,9 +2475,9 @@ function ensureCorrectReturnPointer(fiber, expectedReturnFiber) {
2475 }
2476
2477 function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
2478 - if (__DEV__ && enableDoubleInvokingEffects) {
2479 - // We don't need to re-check for legacy roots here.
2480 - // This function will not be called within legacy roots.
2478 + if (__DEV__ && enableStrictEffects) {
2479 + // We don't need to re-check StrictEffectsMode here.
2480 + // This function is only called if that check has already passed.
2481 switch (fiber.tag) {
2482 case FunctionComponent:
2483 case ForwardRef:
@@ -2509,9 +2509,9 @@ function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
2509 }
2510
2511 function invokePassiveEffectMountInDEV(fiber: Fiber): void {
2512 - if (__DEV__ && enableDoubleInvokingEffects) {
2513 - // We don't need to re-check for legacy roots here.
2514 - // This function will not be called within legacy roots.
2512 + if (__DEV__ && enableStrictEffects) {
2513 + // We don't need to re-check StrictEffectsMode here.
2514 + // This function is only called if that check has already passed.
2515 switch (fiber.tag) {
2516 case FunctionComponent:
2517 case ForwardRef:
@@ -2534,9 +2534,9 @@ function invokePassiveEffectMountInDEV(fiber: Fiber): void {
2534 }
2535
2536 function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
2537 - if (__DEV__ && enableDoubleInvokingEffects) {
2538 - // We don't need to re-check for legacy roots here.
2539 - // This function will not be called within legacy roots.
2537 + if (__DEV__ && enableStrictEffects) {
2538 + // We don't need to re-check StrictEffectsMode here.
2539 + // This function is only called if that check has already passed.
2540 switch (fiber.tag) {
2541 case FunctionComponent:
2542 case ForwardRef:
@@ -2578,9 +2578,9 @@ function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
2578 }
2579
2580 function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
2581 - if (__DEV__ && enableDoubleInvokingEffects) {
2582 - // We don't need to re-check for legacy roots here.
2583 - // This function will not be called within legacy roots.
2581 + if (__DEV__ && enableStrictEffects) {
2582 + // We don't need to re-check StrictEffectsMode here.
2583 + // This function is only called if that check has already passed.
2584 switch (fiber.tag) {
2585 case FunctionComponent:
2586 case ForwardRef:
packages/react-reconciler/src/ReactFiberCommitWork.old.js
+13 -13
@@ -35,7 +35,7 @@ import {
35 enableSuspenseServerRenderer,
36 enableSuspenseCallback,
37 enableScopeAPI,
38 - enableDoubleInvokingEffects,
38 + enableStrictEffects,
39 } from 'shared/ReactFeatureFlags';
40 import {
41 FunctionComponent,
@@ -2475,9 +2475,9 @@ function ensureCorrectReturnPointer(fiber, expectedReturnFiber) {
2475 }
2476
2477 function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
2478 - if (__DEV__ && enableDoubleInvokingEffects) {
2479 - // We don't need to re-check for legacy roots here.
2480 - // This function will not be called within legacy roots.
2478 + if (__DEV__ && enableStrictEffects) {
2479 + // We don't need to re-check StrictEffectsMode here.
2480 + // This function is only called if that check has already passed.
2481 switch (fiber.tag) {
2482 case FunctionComponent:
2483 case ForwardRef:
@@ -2509,9 +2509,9 @@ function invokeLayoutEffectMountInDEV(fiber: Fiber): void {
2509 }
2510
2511 function invokePassiveEffectMountInDEV(fiber: Fiber): void {
2512 - if (__DEV__ && enableDoubleInvokingEffects) {
2513 - // We don't need to re-check for legacy roots here.
2514 - // This function will not be called within legacy roots.
2512 + if (__DEV__ && enableStrictEffects) {
2513 + // We don't need to re-check StrictEffectsMode here.
2514 + // This function is only called if that check has already passed.
2515 switch (fiber.tag) {
2516 case FunctionComponent:
2517 case ForwardRef:
@@ -2534,9 +2534,9 @@ function invokePassiveEffectMountInDEV(fiber: Fiber): void {
2534 }
2535
2536 function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
2537 - if (__DEV__ && enableDoubleInvokingEffects) {
2538 - // We don't need to re-check for legacy roots here.
2539 - // This function will not be called within legacy roots.
2537 + if (__DEV__ && enableStrictEffects) {
2538 + // We don't need to re-check StrictEffectsMode here.
2539 + // This function is only called if that check has already passed.
2540 switch (fiber.tag) {
2541 case FunctionComponent:
2542 case ForwardRef:
@@ -2578,9 +2578,9 @@ function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void {
2578 }
2579
2580 function invokePassiveEffectUnmountInDEV(fiber: Fiber): void {
2581 - if (__DEV__ && enableDoubleInvokingEffects) {
2582 - // We don't need to re-check for legacy roots here.
2583 - // This function will not be called within legacy roots.
2581 + if (__DEV__ && enableStrictEffects) {
2582 + // We don't need to re-check StrictEffectsMode here.
2583 + // This function is only called if that check has already passed.
2584 switch (fiber.tag) {
2585 case FunctionComponent:
2586 case ForwardRef:
packages/react-reconciler/src/ReactFiberHooks.new.js
+15 -11
@@ -29,14 +29,14 @@ import {
29 enableCache,
30 decoupleUpdatePriorityFromScheduler,
31 enableUseRefAccessWarning,
32 - enableDoubleInvokingEffects,
32 + enableStrictEffects,
33 } from 'shared/ReactFeatureFlags';
34
35 import {
36 NoMode,
37 BlockingMode,
38 - ConcurrentMode,
38 DebugTracingMode,
39 + StrictEffectsMode,
40 } from './ReactTypeOfMode';
41 import {
42 NoLane,
@@ -509,8 +509,8 @@ export function bailoutHooks(
509 // complete phase (bubbleProperties).
510 if (
511 __DEV__ &&
512 - enableDoubleInvokingEffects &&
513 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
512 + enableStrictEffects &&
513 + (workInProgress.mode & StrictEffectsMode) !== NoMode
514 ) {
515 workInProgress.flags &= ~(
516 MountPassiveDevEffect |
@@ -1423,8 +1423,8 @@ function mountEffect(
1423 }
1424 if (
1425 __DEV__ &&
1426 - enableDoubleInvokingEffects &&
1427 - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1426 + enableStrictEffects &&
1427 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1428 ) {
1429 return mountEffectImpl(
1430 MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
@@ -1461,8 +1461,8 @@ function mountLayoutEffect(
1461 ): void {
1462 if (
1463 __DEV__ &&
1464 - enableDoubleInvokingEffects &&
1465 - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1464 + enableStrictEffects &&
1465 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1466 ) {
1467 return mountEffectImpl(
1468 MountLayoutDevEffect | UpdateEffect,
@@ -1533,8 +1533,8 @@ function mountImperativeHandle<T>(
1533
1534 if (
1535 __DEV__ &&
1536 - enableDoubleInvokingEffects &&
1537 - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1536 + enableStrictEffects &&
1537 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1538 ) {
1539 return mountEffectImpl(
1540 MountLayoutDevEffect | UpdateEffect,
@@ -1830,7 +1830,11 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
1830 const setId = mountState(id)[1];
1831
1832 if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1833 - if (__DEV__ && enableDoubleInvokingEffects) {
1833 + if (
1834 + __DEV__ &&
1835 + enableStrictEffects &&
1836 + (currentlyRenderingFiber.mode & StrictEffectsMode) === NoMode
1837 + ) {
1838 currentlyRenderingFiber.flags |= MountPassiveDevEffect | PassiveEffect;
1839 } else {
1840 currentlyRenderingFiber.flags |= PassiveEffect;
packages/react-reconciler/src/ReactFiberHooks.old.js
+15 -11
@@ -29,14 +29,14 @@ import {
29 enableCache,
30 decoupleUpdatePriorityFromScheduler,
31 enableUseRefAccessWarning,
32 - enableDoubleInvokingEffects,
32 + enableStrictEffects,
33 } from 'shared/ReactFeatureFlags';
34
35 import {
36 NoMode,
37 BlockingMode,
38 - ConcurrentMode,
38 DebugTracingMode,
39 + StrictEffectsMode,
40 } from './ReactTypeOfMode';
41 import {
42 NoLane,
@@ -509,8 +509,8 @@ export function bailoutHooks(
509 // complete phase (bubbleProperties).
510 if (
511 __DEV__ &&
512 - enableDoubleInvokingEffects &&
513 - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode
512 + enableStrictEffects &&
513 + (workInProgress.mode & StrictEffectsMode) !== NoMode
514 ) {
515 workInProgress.flags &= ~(
516 MountPassiveDevEffect |
@@ -1423,8 +1423,8 @@ function mountEffect(
1423 }
1424 if (
1425 __DEV__ &&
1426 - enableDoubleInvokingEffects &&
1427 - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1426 + enableStrictEffects &&
1427 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1428 ) {
1429 return mountEffectImpl(
1430 MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect,
@@ -1461,8 +1461,8 @@ function mountLayoutEffect(
1461 ): void {
1462 if (
1463 __DEV__ &&
1464 - enableDoubleInvokingEffects &&
1465 - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1464 + enableStrictEffects &&
1465 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1466 ) {
1467 return mountEffectImpl(
1468 MountLayoutDevEffect | UpdateEffect,
@@ -1533,8 +1533,8 @@ function mountImperativeHandle<T>(
1533
1534 if (
1535 __DEV__ &&
1536 - enableDoubleInvokingEffects &&
1537 - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode
1536 + enableStrictEffects &&
1537 + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode
1538 ) {
1539 return mountEffectImpl(
1540 MountLayoutDevEffect | UpdateEffect,
@@ -1830,7 +1830,11 @@ function mountOpaqueIdentifier(): OpaqueIDType | void {
1830 const setId = mountState(id)[1];
1831
1832 if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) {
1833 - if (__DEV__ && enableDoubleInvokingEffects) {
1833 + if (
1834 + __DEV__ &&
1835 + enableStrictEffects &&
1836 + (currentlyRenderingFiber.mode & StrictEffectsMode) === NoMode
1837 + ) {
1838 currentlyRenderingFiber.flags |= MountPassiveDevEffect | PassiveEffect;
1839 } else {
1840 currentlyRenderingFiber.flags |= PassiveEffect;
packages/react-reconciler/src/ReactFiberReconciler.new.js
+11 -4
@@ -75,7 +75,7 @@ import {
75 resetCurrentFiber as resetCurrentDebugFiberInDEV,
76 setCurrentFiber as setCurrentDebugFiberInDEV,
77 } from './ReactCurrentFiber';
78 -import {StrictMode} from './ReactTypeOfMode';
78 +import {StrictLegacyMode} from './ReactTypeOfMode';
79 import {
80 SyncLane,
81 InputDiscreteHydrationLane,
@@ -204,7 +204,7 @@ function findHostInstanceWithWarning(
204 if (hostFiber === null) {
205 return null;
206 }
207 - if (hostFiber.mode & StrictMode) {
207 + if (hostFiber.mode & StrictLegacyMode) {
208 const componentName = getComponentName(fiber.type) || 'Component';
209 if (!didWarnAboutFindNodeInStrictMode[componentName]) {
210 didWarnAboutFindNodeInStrictMode[componentName] = true;
@@ -212,7 +212,7 @@ function findHostInstanceWithWarning(
212 const previousFiber = ReactCurrentFiberCurrent;
213 try {
214 setCurrentDebugFiberInDEV(hostFiber);
215 - if (fiber.mode & StrictMode) {
215 + if (fiber.mode & StrictLegacyMode) {
216 console.error(
217 '%s is deprecated in StrictMode. ' +
218 '%s was passed an instance of %s which is inside StrictMode. ' +
@@ -256,8 +256,15 @@ export function createContainer(
256 tag: RootTag,
257 hydrate: boolean,
258 hydrationCallbacks: null | SuspenseHydrationCallbacks,
259 + strictModeLevelOverride: null | number,
260 ): OpaqueRoot {
260 - return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
261 + return createFiberRoot(
262 + containerInfo,
263 + tag,
264 + hydrate,
265 + hydrationCallbacks,
266 + strictModeLevelOverride,
267 + );
268 }
269
270 export function updateContainer(
packages/react-reconciler/src/ReactFiberReconciler.old.js
+11 -4
@@ -75,7 +75,7 @@ import {
75 resetCurrentFiber as resetCurrentDebugFiberInDEV,
76 setCurrentFiber as setCurrentDebugFiberInDEV,
77 } from './ReactCurrentFiber';
78 -import {StrictMode} from './ReactTypeOfMode';
78 +import {StrictLegacyMode} from './ReactTypeOfMode';
79 import {
80 SyncLane,
81 InputDiscreteHydrationLane,
@@ -204,7 +204,7 @@ function findHostInstanceWithWarning(
204 if (hostFiber === null) {
205 return null;
206 }
207 - if (hostFiber.mode & StrictMode) {
207 + if (hostFiber.mode & StrictLegacyMode) {
208 const componentName = getComponentName(fiber.type) || 'Component';
209 if (!didWarnAboutFindNodeInStrictMode[componentName]) {
210 didWarnAboutFindNodeInStrictMode[componentName] = true;
@@ -212,7 +212,7 @@ function findHostInstanceWithWarning(
212 const previousFiber = ReactCurrentFiberCurrent;
213 try {
214 setCurrentDebugFiberInDEV(hostFiber);
215 - if (fiber.mode & StrictMode) {
215 + if (fiber.mode & StrictLegacyMode) {
216 console.error(
217 '%s is deprecated in StrictMode. ' +
218 '%s was passed an instance of %s which is inside StrictMode. ' +
@@ -256,8 +256,15 @@ export function createContainer(
256 tag: RootTag,
257 hydrate: boolean,
258 hydrationCallbacks: null | SuspenseHydrationCallbacks,
259 + strictModeLevelOverride: null | number,
260 ): OpaqueRoot {
260 - return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
261 + return createFiberRoot(
262 + containerInfo,
263 + tag,
264 + hydrate,
265 + hydrationCallbacks,
266 + strictModeLevelOverride,
267 + );
268 }
269
270 export function updateContainer(
packages/react-reconciler/src/ReactFiberRoot.new.js
+2 -1
@@ -91,6 +91,7 @@ export function createFiberRoot(
91 tag: RootTag,
92 hydrate: boolean,
93 hydrationCallbacks: null | SuspenseHydrationCallbacks,
94 + strictModeLevelOverride: null | number,
95 ): FiberRoot {
96 const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
97 if (enableSuspenseCallback) {
@@ -99,7 +100,7 @@ export function createFiberRoot(
100
101 // Cyclic construction. This cheats the type system right now because
102 // stateNode is any.
102 - const uninitializedFiber = createHostRootFiber(tag);
103 + const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
104 root.current = uninitializedFiber;
105 uninitializedFiber.stateNode = root;
106
packages/react-reconciler/src/ReactFiberRoot.old.js
+2 -1
@@ -91,6 +91,7 @@ export function createFiberRoot(
91 tag: RootTag,
92 hydrate: boolean,
93 hydrationCallbacks: null | SuspenseHydrationCallbacks,
94 + strictModeLevelOverride: null | number,
95 ): FiberRoot {
96 const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
97 if (enableSuspenseCallback) {
@@ -99,7 +100,7 @@ export function createFiberRoot(
100
101 // Cyclic construction. This cheats the type system right now because
102 // stateNode is any.
102 - const uninitializedFiber = createHostRootFiber(tag);
103 + const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
104 root.current = uninitializedFiber;
105 uninitializedFiber.stateNode = root;
106
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+12 -13
@@ -32,7 +32,7 @@ import {
32 enableDebugTracing,
33 enableSchedulingProfiler,
34 disableSchedulerTimeoutInWorkLoop,
35 - enableDoubleInvokingEffects,
35 + enableStrictEffects,
36 skipUnmountedBoundaries,
37 enableNativeEventPriorityInference,
38 } from 'shared/ReactFeatureFlags';
@@ -104,7 +104,7 @@ import {
104 } from './ReactFiber.new';
105 import {
106 NoMode,
107 - StrictMode,
107 + StrictLegacyMode,
108 ProfileMode,
109 BlockingMode,
110 ConcurrentMode,
@@ -2070,7 +2070,7 @@ function commitRootImpl(root, renderPriorityLevel) {
2070 legacyErrorBoundariesThatAlreadyFailed = null;
2071 }
2072
2073 - if (__DEV__ && enableDoubleInvokingEffects) {
2073 + if (__DEV__ && enableStrictEffects) {
2074 if (!rootDidHavePassiveEffects) {
2075 commitDoubleInvokeEffectsInDEV(root.current, false);
2076 }
@@ -2257,7 +2257,7 @@ function flushPassiveEffectsImpl() {
2257 markPassiveEffectsStopped();
2258 }
2259
2260 - if (__DEV__ && enableDoubleInvokingEffects) {
2260 + if (__DEV__ && enableStrictEffects) {
2261 commitDoubleInvokeEffectsInDEV(root.current, true);
2262 }
2263
@@ -2560,11 +2560,10 @@ function commitDoubleInvokeEffectsInDEV(
2560 fiber: Fiber,
2561 hasPassiveEffects: boolean,
2562 ) {
2563 - if (__DEV__ && enableDoubleInvokingEffects) {
2564 - // Never double-invoke effects for legacy roots.
2565 - if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) {
2566 - return;
2567 - }
2563 + if (__DEV__ && enableStrictEffects) {
2564 + // TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
2565 + // so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
2566 + // Maybe not a big deal since this is DEV only behavior.
2567
2568 setCurrentDebugFiberInDEV(fiber);
2569 invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
@@ -2589,9 +2588,9 @@ function invokeEffectsInDev(
2588 fiberFlags: Flags,
2589 invokeEffectFn: (fiber: Fiber) => void,
2590 ): void {
2592 - if (__DEV__ && enableDoubleInvokingEffects) {
2593 - // We don't need to re-check for legacy roots here.
2594 - // This function will not be called within legacy roots.
2591 + if (__DEV__ && enableStrictEffects) {
2592 + // We don't need to re-check StrictEffectsMode here.
2593 + // This function is only called if that check has already passed.
2594
2595 let current = firstChild;
2596 let subtreeRoot = null;
@@ -2934,7 +2933,7 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
2933 if (__DEV__) {
2934 if (
2935 warnsIfNotActing === true &&
2937 - (fiber.mode & StrictMode) !== NoMode &&
2936 + (fiber.mode & StrictLegacyMode) !== NoMode &&
2937 IsSomeRendererActing.current === false &&
2938 IsThisRendererActing.current === false
2939 ) {
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+12 -13
@@ -32,7 +32,7 @@ import {
32 enableDebugTracing,
33 enableSchedulingProfiler,
34 disableSchedulerTimeoutInWorkLoop,
35 - enableDoubleInvokingEffects,
35 + enableStrictEffects,
36 skipUnmountedBoundaries,
37 enableNativeEventPriorityInference,
38 } from 'shared/ReactFeatureFlags';
@@ -104,7 +104,7 @@ import {
104 } from './ReactFiber.old';
105 import {
106 NoMode,
107 - StrictMode,
107 + StrictLegacyMode,
108 ProfileMode,
109 BlockingMode,
110 ConcurrentMode,
@@ -2070,7 +2070,7 @@ function commitRootImpl(root, renderPriorityLevel) {
2070 legacyErrorBoundariesThatAlreadyFailed = null;
2071 }
2072
2073 - if (__DEV__ && enableDoubleInvokingEffects) {
2073 + if (__DEV__ && enableStrictEffects) {
2074 if (!rootDidHavePassiveEffects) {
2075 commitDoubleInvokeEffectsInDEV(root.current, false);
2076 }
@@ -2257,7 +2257,7 @@ function flushPassiveEffectsImpl() {
2257 markPassiveEffectsStopped();
2258 }
2259
2260 - if (__DEV__ && enableDoubleInvokingEffects) {
2260 + if (__DEV__ && enableStrictEffects) {
2261 commitDoubleInvokeEffectsInDEV(root.current, true);
2262 }
2263
@@ -2560,11 +2560,10 @@ function commitDoubleInvokeEffectsInDEV(
2560 fiber: Fiber,
2561 hasPassiveEffects: boolean,
2562 ) {
2563 - if (__DEV__ && enableDoubleInvokingEffects) {
2564 - // Never double-invoke effects for legacy roots.
2565 - if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) {
2566 - return;
2567 - }
2563 + if (__DEV__ && enableStrictEffects) {
2564 + // TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
2565 + // so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
2566 + // Maybe not a big deal since this is DEV only behavior.
2567
2568 setCurrentDebugFiberInDEV(fiber);
2569 invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
@@ -2589,9 +2588,9 @@ function invokeEffectsInDev(
2588 fiberFlags: Flags,
2589 invokeEffectFn: (fiber: Fiber) => void,
2590 ): void {
2592 - if (__DEV__ && enableDoubleInvokingEffects) {
2593 - // We don't need to re-check for legacy roots here.
2594 - // This function will not be called within legacy roots.
2591 + if (__DEV__ && enableStrictEffects) {
2592 + // We don't need to re-check StrictEffectsMode here.
2593 + // This function is only called if that check has already passed.
2594
2595 let current = firstChild;
2596 let subtreeRoot = null;
@@ -2934,7 +2933,7 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void {
2933 if (__DEV__) {
2934 if (
2935 warnsIfNotActing === true &&
2937 - (fiber.mode & StrictMode) !== NoMode &&
2936 + (fiber.mode & StrictLegacyMode) !== NoMode &&
2937 IsSomeRendererActing.current === false &&
2938 IsThisRendererActing.current === false
2939 ) {
packages/react-reconciler/src/ReactStrictModeWarnings.new.js
+5 -5
@@ -14,7 +14,7 @@ import {
14 setCurrentFiber as setCurrentDebugFiberInDEV,
15 } from './ReactCurrentFiber';
16 import getComponentName from 'shared/getComponentName';
17 -import {StrictMode} from './ReactTypeOfMode';
17 +import {StrictLegacyMode} from './ReactTypeOfMode';
18
19 type FiberArray = Array<Fiber>;
20 type FiberToFiberComponentsMap = Map<Fiber, FiberArray>;
@@ -33,7 +33,7 @@ if (__DEV__) {
33
34 let node = fiber;
35 while (node !== null) {
36 - if (node.mode & StrictMode) {
36 + if (node.mode & StrictLegacyMode) {
37 maybeStrictRoot = node;
38 }
39 node = node.return;
@@ -78,7 +78,7 @@ if (__DEV__) {
78 }
79
80 if (
81 - fiber.mode & StrictMode &&
81 + fiber.mode & StrictLegacyMode &&
82 typeof instance.UNSAFE_componentWillMount === 'function'
83 ) {
84 pendingUNSAFE_ComponentWillMountWarnings.push(fiber);
@@ -92,7 +92,7 @@ if (__DEV__) {
92 }
93
94 if (
95 - fiber.mode & StrictMode &&
95 + fiber.mode & StrictLegacyMode &&
96 typeof instance.UNSAFE_componentWillReceiveProps === 'function'
97 ) {
98 pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber);
@@ -106,7 +106,7 @@ if (__DEV__) {
106 }
107
108 if (
109 - fiber.mode & StrictMode &&
109 + fiber.mode & StrictLegacyMode &&
110 typeof instance.UNSAFE_componentWillUpdate === 'function'
111 ) {
112 pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber);
packages/react-reconciler/src/ReactStrictModeWarnings.old.js
+5 -5
@@ -14,7 +14,7 @@ import {
14 setCurrentFiber as setCurrentDebugFiberInDEV,
15 } from './ReactCurrentFiber';
16 import getComponentName from 'shared/getComponentName';
17 -import {StrictMode} from './ReactTypeOfMode';
17 +import {StrictLegacyMode} from './ReactTypeOfMode';
18
19 type FiberArray = Array<Fiber>;
20 type FiberToFiberComponentsMap = Map<Fiber, FiberArray>;
@@ -33,7 +33,7 @@ if (__DEV__) {
33
34 let node = fiber;
35 while (node !== null) {
36 - if (node.mode & StrictMode) {
36 + if (node.mode & StrictLegacyMode) {
37 maybeStrictRoot = node;
38 }
39 node = node.return;
@@ -78,7 +78,7 @@ if (__DEV__) {
78 }
79
80 if (
81 - fiber.mode & StrictMode &&
81 + fiber.mode & StrictLegacyMode &&
82 typeof instance.UNSAFE_componentWillMount === 'function'
83 ) {
84 pendingUNSAFE_ComponentWillMountWarnings.push(fiber);
@@ -92,7 +92,7 @@ if (__DEV__) {
92 }
93
94 if (
95 - fiber.mode & StrictMode &&
95 + fiber.mode & StrictLegacyMode &&
96 typeof instance.UNSAFE_componentWillReceiveProps === 'function'
97 ) {
98 pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber);
@@ -106,7 +106,7 @@ if (__DEV__) {
106 }
107
108 if (
109 - fiber.mode & StrictMode &&
109 + fiber.mode & StrictLegacyMode &&
110 typeof instance.UNSAFE_componentWillUpdate === 'function'
111 ) {
112 pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber);
packages/react-reconciler/src/ReactTypeOfMode.js
+8 -8
@@ -9,11 +9,11 @@
9
10 export type TypeOfMode = number;
11
12 -export const NoMode = 0b00000;
13 -export const StrictMode = 0b00001;
14 -// TODO: Remove BlockingMode and ConcurrentMode by reading from the root
15 -// tag instead
16 -export const BlockingMode = 0b00010;
17 -export const ConcurrentMode = 0b00100;
18 -export const ProfileMode = 0b01000;
19 -export const DebugTracingMode = 0b10000;
12 +export const NoMode = /* */ 0b000000;
13 +// TODO: Remove BlockingMode and ConcurrentMode by reading from the root tag instead
14 +export const BlockingMode = /* */ 0b000001;
15 +export const ConcurrentMode = /* */ 0b000010;
16 +export const ProfileMode = /* */ 0b000100;
17 +export const DebugTracingMode = /* */ 0b001000;
18 +export const StrictLegacyMode = /* */ 0b010000;
19 +export const StrictEffectsMode = /* */ 0b100000;
packages/react-reconciler/src/ReactUpdateQueue.new.js
+3 -3
@@ -104,7 +104,7 @@ import {Callback, ShouldCapture, DidCapture} from './ReactFiberFlags';
104
105 import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
106
107 -import {StrictMode} from './ReactTypeOfMode';
107 +import {StrictLegacyMode} from './ReactTypeOfMode';
108 import {
109 markSkippedUpdateLanes,
110 isInterleavedUpdate,
@@ -392,7 +392,7 @@ function getStateFromUpdate<State>(
392 if (__DEV__) {
393 if (
394 debugRenderPhaseSideEffectsForStrictMode &&
395 - workInProgress.mode & StrictMode
395 + workInProgress.mode & StrictLegacyMode
396 ) {
397 disableLogs();
398 try {
@@ -425,7 +425,7 @@ function getStateFromUpdate<State>(
425 if (__DEV__) {
426 if (
427 debugRenderPhaseSideEffectsForStrictMode &&
428 - workInProgress.mode & StrictMode
428 + workInProgress.mode & StrictLegacyMode
429 ) {
430 disableLogs();
431 try {
packages/react-reconciler/src/ReactUpdateQueue.old.js
+3 -3
@@ -104,7 +104,7 @@ import {Callback, ShouldCapture, DidCapture} from './ReactFiberFlags';
104
105 import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags';
106
107 -import {StrictMode} from './ReactTypeOfMode';
107 +import {StrictLegacyMode} from './ReactTypeOfMode';
108 import {
109 markSkippedUpdateLanes,
110 isInterleavedUpdate,
@@ -392,7 +392,7 @@ function getStateFromUpdate<State>(
392 if (__DEV__) {
393 if (
394 debugRenderPhaseSideEffectsForStrictMode &&
395 - workInProgress.mode & StrictMode
395 + workInProgress.mode & StrictLegacyMode
396 ) {
397 disableLogs();
398 try {
@@ -425,7 +425,7 @@ function getStateFromUpdate<State>(
425 if (__DEV__) {
426 if (
427 debugRenderPhaseSideEffectsForStrictMode &&
428 - workInProgress.mode & StrictMode
428 + workInProgress.mode & StrictLegacyMode
429 ) {
430 disableLogs();
431 try {
packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.internal.js deleted
-747
@@ -1,747 +0,0 @@
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__;
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 in legacy mode', () => {
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 not double invoke class lifecycles in legacy mode', () => {
60 - class App extends React.PureComponent {
61 - componentDidMount() {
62 - Scheduler.unstable_yieldValue('componentDidMount');
63 - }
64 -
65 - componentDidUpdate() {
66 - Scheduler.unstable_yieldValue('componentDidUpdate');
67 - }
68 -
69 - componentWillUnmount() {
70 - Scheduler.unstable_yieldValue('componentWillUnmount');
71 - }
72 -
73 - render() {
74 - return this.props.text;
75 - }
76 - }
77 -
78 - ReactNoop.act(() => {
79 - ReactNoop.renderLegacySyncRoot(<App text={'mount'} />);
80 - });
81 -
82 - expect(Scheduler).toHaveYielded(['componentDidMount']);
83 - });
84 -
85 - it('should flush double-invoked effects within the same frame as layout effects if there are no passive effects', () => {
86 - function ComponentWithEffects({label}) {
87 - React.useLayoutEffect(() => {
88 - Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);
89 - return () =>
90 - Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);
91 - });
92 -
93 - return label;
94 - }
95 -
96 - ReactNoop.act(() => {
97 - ReactNoop.render(
98 - <>
99 - <ComponentWithEffects label={'one'} />
100 - </>,
101 - );
102 -
103 - if (shouldDoubleInvokingEffects()) {
104 - expect(Scheduler).toFlushUntilNextPaint([
105 - 'useLayoutEffect mount "one"',
106 - 'useLayoutEffect unmount "one"',
107 - 'useLayoutEffect mount "one"',
108 - ]);
109 - } else {
110 - expect(Scheduler).toFlushUntilNextPaint([
111 - 'useLayoutEffect mount "one"',
112 - ]);
113 - }
114 - });
115 -
116 - ReactNoop.act(() => {
117 - ReactNoop.render(
118 - <>
119 - <ComponentWithEffects label={'one'} />
120 - <ComponentWithEffects label={'two'} />
121 - </>,
122 - );
123 -
124 - if (shouldDoubleInvokingEffects()) {
125 - expect(Scheduler).toFlushUntilNextPaint([
126 - // Cleanup and re-run "one" (and "two") since there is no dependencies array.
127 - 'useLayoutEffect unmount "one"',
128 - 'useLayoutEffect mount "one"',
129 - 'useLayoutEffect mount "two"',
130 -
131 - // Since "two" is new, it should be double-invoked.
132 - 'useLayoutEffect unmount "two"',
133 - 'useLayoutEffect mount "two"',
134 - ]);
135 - } else {
136 - expect(Scheduler).toFlushUntilNextPaint([
137 - 'useLayoutEffect unmount "one"',
138 - 'useLayoutEffect mount "one"',
139 - 'useLayoutEffect mount "two"',
140 - ]);
141 - }
142 - });
143 - });
144 -
145 - // This test also verifies that double-invoked effects flush synchronously
146 - // within the same frame as passive effects.
147 - it('should double invoke effects only for newly mounted components', () => {
148 - function ComponentWithEffects({label}) {
149 - React.useEffect(() => {
150 - Scheduler.unstable_yieldValue(`useEffect mount "${label}"`);
151 - return () =>
152 - Scheduler.unstable_yieldValue(`useEffect unmount "${label}"`);
153 - });
154 -
155 - React.useLayoutEffect(() => {
156 - Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);
157 - return () =>
158 - Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);
159 - });
160 -
161 - return label;
162 - }
163 -
164 - ReactNoop.act(() => {
165 - ReactNoop.render(
166 - <>
167 - <ComponentWithEffects label={'one'} />
168 - </>,
169 - );
170 -
171 - if (shouldDoubleInvokingEffects()) {
172 - expect(Scheduler).toFlushAndYieldThrough([
173 - 'useLayoutEffect mount "one"',
174 - ]);
175 - expect(Scheduler).toFlushAndYield([
176 - 'useEffect mount "one"',
177 - 'useLayoutEffect unmount "one"',
178 - 'useEffect unmount "one"',
179 - 'useLayoutEffect mount "one"',
180 - 'useEffect mount "one"',
181 - ]);
182 - } else {
183 - expect(Scheduler).toFlushAndYieldThrough([
184 - 'useLayoutEffect mount "one"',
185 - ]);
186 - expect(Scheduler).toFlushAndYield(['useEffect mount "one"']);
187 - }
188 - });
189 -
190 - ReactNoop.act(() => {
191 - ReactNoop.render(
192 - <>
193 - <ComponentWithEffects label={'one'} />
194 - <ComponentWithEffects label={'two'} />
195 - </>,
196 - );
197 -
198 - if (shouldDoubleInvokingEffects()) {
199 - expect(Scheduler).toFlushAndYieldThrough([
200 - // Cleanup and re-run "one" (and "two") since there is no dependencies array.
201 - 'useLayoutEffect unmount "one"',
202 - 'useLayoutEffect mount "one"',
203 - 'useLayoutEffect mount "two"',
204 - ]);
205 - expect(Scheduler).toFlushAndYield([
206 - 'useEffect unmount "one"',
207 - 'useEffect mount "one"',
208 - 'useEffect mount "two"',
209 -
210 - // Since "two" is new, it should be double-invoked.
211 - 'useLayoutEffect unmount "two"',
212 - 'useEffect unmount "two"',
213 - 'useLayoutEffect mount "two"',
214 - 'useEffect mount "two"',
215 - ]);
216 - } else {
217 - expect(Scheduler).toFlushAndYieldThrough([
218 - 'useLayoutEffect unmount "one"',
219 - 'useLayoutEffect mount "one"',
220 - 'useLayoutEffect mount "two"',
221 - ]);
222 - expect(Scheduler).toFlushAndYield([
223 - 'useEffect unmount "one"',
224 - 'useEffect mount "one"',
225 - 'useEffect mount "two"',
226 - ]);
227 - }
228 - });
229 - });
230 -
231 - it('double invoking for effects for modern roots', () => {
232 - function App({text}) {
233 - React.useEffect(() => {
234 - Scheduler.unstable_yieldValue('useEffect mount');
235 - return () => Scheduler.unstable_yieldValue('useEffect unmount');
236 - });
237 -
238 - React.useLayoutEffect(() => {
239 - Scheduler.unstable_yieldValue('useLayoutEffect mount');
240 - return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
241 - });
242 -
243 - return text;
244 - }
245 - ReactNoop.act(() => {
246 - ReactNoop.render(<App text={'mount'} />);
247 - });
248 -
249 - if (shouldDoubleInvokingEffects()) {
250 - expect(Scheduler).toHaveYielded([
251 - 'useLayoutEffect mount',
252 - 'useEffect mount',
253 - 'useLayoutEffect unmount',
254 - 'useEffect unmount',
255 - 'useLayoutEffect mount',
256 - 'useEffect mount',
257 - ]);
258 - } else {
259 - expect(Scheduler).toHaveYielded([
260 - 'useLayoutEffect mount',
261 - 'useEffect mount',
262 - ]);
263 - }
264 -
265 - ReactNoop.act(() => {
266 - ReactNoop.render(<App text={'update'} />);
267 - });
268 -
269 - expect(Scheduler).toHaveYielded([
270 - 'useLayoutEffect unmount',
271 - 'useLayoutEffect mount',
272 - 'useEffect unmount',
273 - 'useEffect mount',
274 - ]);
275 -
276 - ReactNoop.act(() => {
277 - ReactNoop.render(null);
278 - });
279 -
280 - expect(Scheduler).toHaveYielded([
281 - 'useLayoutEffect unmount',
282 - 'useEffect unmount',
283 - ]);
284 - });
285 -
286 - it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {
287 - function App({text}) {
288 - React.useEffect(() => {
289 - Scheduler.unstable_yieldValue('useEffect One mount');
290 - return () => Scheduler.unstable_yieldValue('useEffect One unmount');
291 - });
292 -
293 - React.useEffect(() => {
294 - Scheduler.unstable_yieldValue('useEffect Two mount');
295 - return () => Scheduler.unstable_yieldValue('useEffect Two unmount');
296 - });
297 -
298 - return text;
299 - }
300 -
301 - ReactNoop.act(() => {
302 - ReactNoop.render(<App text={'mount'} />);
303 - });
304 -
305 - if (shouldDoubleInvokingEffects()) {
306 - expect(Scheduler).toHaveYielded([
307 - 'useEffect One mount',
308 - 'useEffect Two mount',
309 - 'useEffect One unmount',
310 - 'useEffect Two unmount',
311 - 'useEffect One mount',
312 - 'useEffect Two mount',
313 - ]);
314 - } else {
315 - expect(Scheduler).toHaveYielded([
316 - 'useEffect One mount',
317 - 'useEffect Two mount',
318 - ]);
319 - }
320 -
321 - ReactNoop.act(() => {
322 - ReactNoop.render(<App text={'update'} />);
323 - });
324 -
325 - expect(Scheduler).toHaveYielded([
326 - 'useEffect One unmount',
327 - 'useEffect Two unmount',
328 - 'useEffect One mount',
329 - 'useEffect Two mount',
330 - ]);
331 -
332 - ReactNoop.act(() => {
333 - ReactNoop.render(null);
334 - });
335 -
336 - expect(Scheduler).toHaveYielded([
337 - 'useEffect One unmount',
338 - 'useEffect Two unmount',
339 - ]);
340 - });
341 -
342 - it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {
343 - function App({text}) {
344 - React.useLayoutEffect(() => {
345 - Scheduler.unstable_yieldValue('useLayoutEffect One mount');
346 - return () =>
347 - Scheduler.unstable_yieldValue('useLayoutEffect One unmount');
348 - });
349 -
350 - React.useLayoutEffect(() => {
351 - Scheduler.unstable_yieldValue('useLayoutEffect Two mount');
352 - return () =>
353 - Scheduler.unstable_yieldValue('useLayoutEffect Two unmount');
354 - });
355 -
356 - return text;
357 - }
358 -
359 - ReactNoop.act(() => {
360 - ReactNoop.render(<App text={'mount'} />);
361 - });
362 -
363 - if (shouldDoubleInvokingEffects()) {
364 - expect(Scheduler).toHaveYielded([
365 - 'useLayoutEffect One mount',
366 - 'useLayoutEffect Two mount',
367 - 'useLayoutEffect One unmount',
368 - 'useLayoutEffect Two unmount',
369 - 'useLayoutEffect One mount',
370 - 'useLayoutEffect Two mount',
371 - ]);
372 - } else {
373 - expect(Scheduler).toHaveYielded([
374 - 'useLayoutEffect One mount',
375 - 'useLayoutEffect Two mount',
376 - ]);
377 - }
378 -
379 - ReactNoop.act(() => {
380 - ReactNoop.render(<App text={'update'} />);
381 - });
382 -
383 - expect(Scheduler).toHaveYielded([
384 - 'useLayoutEffect One unmount',
385 - 'useLayoutEffect Two unmount',
386 - 'useLayoutEffect One mount',
387 - 'useLayoutEffect Two mount',
388 - ]);
389 -
390 - ReactNoop.act(() => {
391 - ReactNoop.render(null);
392 - });
393 -
394 - expect(Scheduler).toHaveYielded([
395 - 'useLayoutEffect One unmount',
396 - 'useLayoutEffect Two unmount',
397 - ]);
398 - });
399 -
400 - it('useEffect and useLayoutEffect is called twice when there is no unmount', () => {
401 - function App({text}) {
402 - React.useEffect(() => {
403 - Scheduler.unstable_yieldValue('useEffect mount');
404 - });
405 -
406 - React.useLayoutEffect(() => {
407 - Scheduler.unstable_yieldValue('useLayoutEffect mount');
408 - });
409 -
410 - return text;
411 - }
412 -
413 - ReactNoop.act(() => {
414 - ReactNoop.render(<App text={'mount'} />);
415 - });
416 -
417 - if (shouldDoubleInvokingEffects()) {
418 - expect(Scheduler).toHaveYielded([
419 - 'useLayoutEffect mount',
420 - 'useEffect mount',
421 - 'useLayoutEffect mount',
422 - 'useEffect mount',
423 - ]);
424 - } else {
425 - expect(Scheduler).toHaveYielded([
426 - 'useLayoutEffect mount',
427 - 'useEffect mount',
428 - ]);
429 - }
430 -
431 - ReactNoop.act(() => {
432 - ReactNoop.render(<App text={'update'} />);
433 - });
434 -
435 - expect(Scheduler).toHaveYielded([
436 - 'useLayoutEffect mount',
437 - 'useEffect mount',
438 - ]);
439 -
440 - ReactNoop.act(() => {
441 - ReactNoop.render(null);
442 - });
443 -
444 - expect(Scheduler).toHaveYielded([]);
445 - });
446 -
447 - it('passes the right context to class component lifecycles', () => {
448 - class App extends React.PureComponent {
449 - test() {}
450 -
451 - componentDidMount() {
452 - this.test();
453 - Scheduler.unstable_yieldValue('componentDidMount');
454 - }
455 -
456 - componentDidUpdate() {
457 - this.test();
458 - Scheduler.unstable_yieldValue('componentDidUpdate');
459 - }
460 -
461 - componentWillUnmount() {
462 - this.test();
463 - Scheduler.unstable_yieldValue('componentWillUnmount');
464 - }
465 -
466 - render() {
467 - return null;
468 - }
469 - }
470 -
471 - ReactNoop.act(() => {
472 - ReactNoop.render(<App />);
473 - });
474 -
475 - if (shouldDoubleInvokingEffects()) {
476 - expect(Scheduler).toHaveYielded([
477 - 'componentDidMount',
478 - 'componentWillUnmount',
479 - 'componentDidMount',
480 - ]);
481 - } else {
482 - expect(Scheduler).toHaveYielded(['componentDidMount']);
483 - }
484 - });
485 -
486 - it('double invoking works for class components', () => {
487 - class App extends React.PureComponent {
488 - componentDidMount() {
489 - Scheduler.unstable_yieldValue('componentDidMount');
490 - }
491 -
492 - componentDidUpdate() {
493 - Scheduler.unstable_yieldValue('componentDidUpdate');
494 - }
495 -
496 - componentWillUnmount() {
497 - Scheduler.unstable_yieldValue('componentWillUnmount');
498 - }
499 -
500 - render() {
501 - return this.props.text;
502 - }
503 - }
504 -
505 - ReactNoop.act(() => {
506 - ReactNoop.render(<App text={'mount'} />);
507 - });
508 -
509 - if (shouldDoubleInvokingEffects()) {
510 - expect(Scheduler).toHaveYielded([
511 - 'componentDidMount',
512 - 'componentWillUnmount',
513 - 'componentDidMount',
514 - ]);
515 - } else {
516 - expect(Scheduler).toHaveYielded(['componentDidMount']);
517 - }
518 -
519 - ReactNoop.act(() => {
520 - ReactNoop.render(<App text={'update'} />);
521 - });
522 -
523 - expect(Scheduler).toHaveYielded(['componentDidUpdate']);
524 -
525 - ReactNoop.act(() => {
526 - ReactNoop.render(null);
527 - });
528 -
529 - expect(Scheduler).toHaveYielded(['componentWillUnmount']);
530 - });
531 -
532 - it('double flushing passive effects only results in one double invoke', () => {
533 - function App({text}) {
534 - const [state, setState] = React.useState(0);
535 - React.useEffect(() => {
536 - if (state !== 1) {
537 - setState(1);
538 - }
539 - Scheduler.unstable_yieldValue('useEffect mount');
540 - return () => Scheduler.unstable_yieldValue('useEffect unmount');
541 - });
542 -
543 - React.useLayoutEffect(() => {
544 - Scheduler.unstable_yieldValue('useLayoutEffect mount');
545 - return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
546 - });
547 -
548 - Scheduler.unstable_yieldValue(text);
549 - return text;
550 - }
551 -
552 - ReactNoop.act(() => {
553 - ReactNoop.render(<App text={'mount'} />);
554 - });
555 -
556 - if (shouldDoubleInvokingEffects()) {
557 - expect(Scheduler).toHaveYielded([
558 - 'mount',
559 - 'useLayoutEffect mount',
560 - 'useEffect mount',
561 - 'useLayoutEffect unmount',
562 - 'useEffect unmount',
563 - 'useLayoutEffect mount',
564 - 'useEffect mount',
565 - 'mount',
566 - 'useLayoutEffect unmount',
567 - 'useLayoutEffect mount',
568 - 'useEffect unmount',
569 - 'useEffect mount',
570 - ]);
571 - } else {
572 - expect(Scheduler).toHaveYielded([
573 - 'mount',
574 - 'useLayoutEffect mount',
575 - 'useEffect mount',
576 - 'mount',
577 - 'useLayoutEffect unmount',
578 - 'useLayoutEffect mount',
579 - 'useEffect unmount',
580 - 'useEffect mount',
581 - ]);
582 - }
583 - });
584 -
585 - it('newly mounted components after initial mount get double invoked', () => {
586 - let _setShowChild;
587 - function Child() {
588 - React.useEffect(() => {
589 - Scheduler.unstable_yieldValue('Child useEffect mount');
590 - return () => Scheduler.unstable_yieldValue('Child useEffect unmount');
591 - });
592 - React.useLayoutEffect(() => {
593 - Scheduler.unstable_yieldValue('Child useLayoutEffect mount');
594 - return () =>
595 - Scheduler.unstable_yieldValue('Child useLayoutEffect unmount');
596 - });
597 -
598 - return null;
599 - }
600 -
601 - function App() {
602 - const [showChild, setShowChild] = React.useState(false);
603 - _setShowChild = setShowChild;
604 - React.useEffect(() => {
605 - Scheduler.unstable_yieldValue('App useEffect mount');
606 - return () => Scheduler.unstable_yieldValue('App useEffect unmount');
607 - });
608 - React.useLayoutEffect(() => {
609 - Scheduler.unstable_yieldValue('App useLayoutEffect mount');
610 - return () =>
611 - Scheduler.unstable_yieldValue('App useLayoutEffect unmount');
612 - });
613 -
614 - return showChild && <Child />;
615 - }
616 -
617 - ReactNoop.act(() => {
618 - ReactNoop.render(<App />);
619 - });
620 -
621 - if (shouldDoubleInvokingEffects()) {
622 - expect(Scheduler).toHaveYielded([
623 - 'App useLayoutEffect mount',
624 - 'App useEffect mount',
625 - 'App useLayoutEffect unmount',
626 - 'App useEffect unmount',
627 - 'App useLayoutEffect mount',
628 - 'App useEffect mount',
629 - ]);
630 - } else {
631 - expect(Scheduler).toHaveYielded([
632 - 'App useLayoutEffect mount',
633 - 'App useEffect mount',
634 - ]);
635 - }
636 -
637 - ReactNoop.act(() => {
638 - _setShowChild(true);
639 - });
640 -
641 - if (shouldDoubleInvokingEffects()) {
642 - expect(Scheduler).toHaveYielded([
643 - 'App useLayoutEffect unmount',
644 - 'Child useLayoutEffect mount',
645 - 'App useLayoutEffect mount',
646 - 'App useEffect unmount',
647 - 'Child useEffect mount',
648 - 'App useEffect mount',
649 - 'Child useLayoutEffect unmount',
650 - 'Child useEffect unmount',
651 - 'Child useLayoutEffect mount',
652 - 'Child useEffect mount',
653 - ]);
654 - } else {
655 - expect(Scheduler).toHaveYielded([
656 - 'App useLayoutEffect unmount',
657 - 'Child useLayoutEffect mount',
658 - 'App useLayoutEffect mount',
659 - 'App useEffect unmount',
660 - 'Child useEffect mount',
661 - 'App useEffect mount',
662 - ]);
663 - }
664 - });
665 -
666 - it('classes and functions are double invoked together correctly', () => {
667 - class ClassChild extends React.PureComponent {
668 - componentDidMount() {
669 - Scheduler.unstable_yieldValue('componentDidMount');
670 - }
671 -
672 - componentWillUnmount() {
673 - Scheduler.unstable_yieldValue('componentWillUnmount');
674 - }
675 -
676 - render() {
677 - return this.props.text;
678 - }
679 - }
680 -
681 - function FunctionChild({text}) {
682 - React.useEffect(() => {
683 - Scheduler.unstable_yieldValue('useEffect mount');
684 - return () => Scheduler.unstable_yieldValue('useEffect unmount');
685 - });
686 - React.useLayoutEffect(() => {
687 - Scheduler.unstable_yieldValue('useLayoutEffect mount');
688 - return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
689 - });
690 - return text;
691 - }
692 -
693 - function App({text}) {
694 - return (
695 - <>
696 - <ClassChild text={text} />
697 - <FunctionChild text={text} />
698 - </>
699 - );
700 - }
701 -
702 - ReactNoop.act(() => {
703 - ReactNoop.render(<App text={'mount'} />);
704 - });
705 -
706 - if (shouldDoubleInvokingEffects()) {
707 - expect(Scheduler).toHaveYielded([
708 - 'componentDidMount',
709 - 'useLayoutEffect mount',
710 - 'useEffect mount',
711 - 'componentWillUnmount',
712 - 'useLayoutEffect unmount',
713 - 'useEffect unmount',
714 - 'componentDidMount',
715 - 'useLayoutEffect mount',
716 - 'useEffect mount',
717 - ]);
718 - } else {
719 - expect(Scheduler).toHaveYielded([
720 - 'componentDidMount',
721 - 'useLayoutEffect mount',
722 - 'useEffect mount',
723 - ]);
724 - }
725 -
726 - ReactNoop.act(() => {
727 - ReactNoop.render(<App text={'mount'} />);
728 - });
729 -
730 - expect(Scheduler).toHaveYielded([
731 - 'useLayoutEffect unmount',
732 - 'useLayoutEffect mount',
733 - 'useEffect unmount',
734 - 'useEffect mount',
735 - ]);
736 -
737 - ReactNoop.act(() => {
738 - ReactNoop.render(null);
739 - });
740 -
741 - expect(Scheduler).toHaveYielded([
742 - 'componentWillUnmount',
743 - 'useLayoutEffect unmount',
744 - 'useEffect unmount',
745 - ]);
746 - });
747 -});
packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js renamed
+3 -2
@@ -14,7 +14,7 @@ let ReactTestRenderer;
14 let Scheduler;
15 let act;
16
17 -describe('ReactDoubleInvokeEvents', () => {
17 +describe('StrictEffectsMode', () => {
18 beforeEach(() => {
19 jest.resetModules();
20 React = require('react');
@@ -27,7 +27,8 @@ describe('ReactDoubleInvokeEvents', () => {
27 return gate(
28 flags =>
29 flags.build === 'development' &&
30 - flags.enableDoubleInvokingEffects &&
30 + flags.enableStrictEffects &&
31 + flags.createRootStrictEffectsByDefault &&
32 flags.dfsEffectsRefactor,
33 );
34 }
packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js new
+635
@@ -0,0 +1,635 @@
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 ReactNoop;
14 +let Scheduler;
15 +
16 +describe('StrictEffectsMode defaults', () => {
17 + beforeEach(() => {
18 + jest.resetModules();
19 +
20 + React = require('react');
21 + ReactNoop = require('react-noop-renderer');
22 + Scheduler = require('scheduler');
23 +
24 + const ReactFeatureFlags = require('shared/ReactFeatureFlags');
25 + ReactFeatureFlags.enableStrictEffects = __DEV__;
26 + ReactFeatureFlags.createRootStrictEffectsByDefault = __DEV__;
27 + });
28 +
29 + it('should not double invoke effects in legacy mode', () => {
30 + function App({text}) {
31 + React.useEffect(() => {
32 + Scheduler.unstable_yieldValue('useEffect mount');
33 + return () => Scheduler.unstable_yieldValue('useEffect unmount');
34 + });
35 +
36 + React.useLayoutEffect(() => {
37 + Scheduler.unstable_yieldValue('useLayoutEffect mount');
38 + return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
39 + });
40 +
41 + return text;
42 + }
43 +
44 + ReactNoop.act(() => {
45 + ReactNoop.renderLegacySyncRoot(<App text={'mount'} />);
46 + });
47 +
48 + expect(Scheduler).toHaveYielded([
49 + 'useLayoutEffect mount',
50 + 'useEffect mount',
51 + ]);
52 + });
53 +
54 + it('should not double invoke class lifecycles in legacy mode', () => {
55 + class App extends React.PureComponent {
56 + componentDidMount() {
57 + Scheduler.unstable_yieldValue('componentDidMount');
58 + }
59 +
60 + componentDidUpdate() {
61 + Scheduler.unstable_yieldValue('componentDidUpdate');
62 + }
63 +
64 + componentWillUnmount() {
65 + Scheduler.unstable_yieldValue('componentWillUnmount');
66 + }
67 +
68 + render() {
69 + return this.props.text;
70 + }
71 + }
72 +
73 + ReactNoop.act(() => {
74 + ReactNoop.renderLegacySyncRoot(<App text={'mount'} />);
75 + });
76 +
77 + expect(Scheduler).toHaveYielded(['componentDidMount']);
78 + });
79 +
80 + if (__DEV__) {
81 + it('should flush double-invoked effects within the same frame as layout effects if there are no passive effects', () => {
82 + function ComponentWithEffects({label}) {
83 + React.useLayoutEffect(() => {
84 + Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);
85 + return () =>
86 + Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);
87 + });
88 +
89 + return label;
90 + }
91 +
92 + ReactNoop.act(() => {
93 + ReactNoop.render(
94 + <>
95 + <ComponentWithEffects label={'one'} />
96 + </>,
97 + );
98 +
99 + expect(Scheduler).toFlushUntilNextPaint([
100 + 'useLayoutEffect mount "one"',
101 + 'useLayoutEffect unmount "one"',
102 + 'useLayoutEffect mount "one"',
103 + ]);
104 + });
105 +
106 + ReactNoop.act(() => {
107 + ReactNoop.render(
108 + <>
109 + <ComponentWithEffects label={'one'} />
110 + <ComponentWithEffects label={'two'} />
111 + </>,
112 + );
113 +
114 + expect(Scheduler).toFlushUntilNextPaint([
115 + // Cleanup and re-run "one" (and "two") since there is no dependencies array.
116 + 'useLayoutEffect unmount "one"',
117 + 'useLayoutEffect mount "one"',
118 + 'useLayoutEffect mount "two"',
119 +
120 + // Since "two" is new, it should be double-invoked.
121 + 'useLayoutEffect unmount "two"',
122 + 'useLayoutEffect mount "two"',
123 + ]);
124 + });
125 + });
126 +
127 + // This test also verifies that double-invoked effects flush synchronously
128 + // within the same frame as passive effects.
129 + it('should double invoke effects only for newly mounted components', () => {
130 + function ComponentWithEffects({label}) {
131 + React.useEffect(() => {
132 + Scheduler.unstable_yieldValue(`useEffect mount "${label}"`);
133 + return () =>
134 + Scheduler.unstable_yieldValue(`useEffect unmount "${label}"`);
135 + });
136 +
137 + React.useLayoutEffect(() => {
138 + Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`);
139 + return () =>
140 + Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`);
141 + });
142 +
143 + return label;
144 + }
145 +
146 + ReactNoop.act(() => {
147 + ReactNoop.render(
148 + <>
149 + <ComponentWithEffects label={'one'} />
150 + </>,
151 + );
152 +
153 + expect(Scheduler).toFlushAndYieldThrough([
154 + 'useLayoutEffect mount "one"',
155 + ]);
156 + expect(Scheduler).toFlushAndYield([
157 + 'useEffect mount "one"',
158 + 'useLayoutEffect unmount "one"',
159 + 'useEffect unmount "one"',
160 + 'useLayoutEffect mount "one"',
161 + 'useEffect mount "one"',
162 + ]);
163 + });
164 +
165 + ReactNoop.act(() => {
166 + ReactNoop.render(
167 + <>
168 + <ComponentWithEffects label={'one'} />
169 + <ComponentWithEffects label={'two'} />
170 + </>,
171 + );
172 +
173 + expect(Scheduler).toFlushAndYieldThrough([
174 + // Cleanup and re-run "one" (and "two") since there is no dependencies array.
175 + 'useLayoutEffect unmount "one"',
176 + 'useLayoutEffect mount "one"',
177 + 'useLayoutEffect mount "two"',
178 + ]);
179 + expect(Scheduler).toFlushAndYield([
180 + 'useEffect unmount "one"',
181 + 'useEffect mount "one"',
182 + 'useEffect mount "two"',
183 +
184 + // Since "two" is new, it should be double-invoked.
185 + 'useLayoutEffect unmount "two"',
186 + 'useEffect unmount "two"',
187 + 'useLayoutEffect mount "two"',
188 + 'useEffect mount "two"',
189 + ]);
190 + });
191 + });
192 +
193 + it('double invoking for effects for modern roots', () => {
194 + function App({text}) {
195 + React.useEffect(() => {
196 + Scheduler.unstable_yieldValue('useEffect mount');
197 + return () => Scheduler.unstable_yieldValue('useEffect unmount');
198 + });
199 +
200 + React.useLayoutEffect(() => {
201 + Scheduler.unstable_yieldValue('useLayoutEffect mount');
202 + return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
203 + });
204 +
205 + return text;
206 + }
207 + ReactNoop.act(() => {
208 + ReactNoop.render(<App text={'mount'} />);
209 + });
210 +
211 + expect(Scheduler).toHaveYielded([
212 + 'useLayoutEffect mount',
213 + 'useEffect mount',
214 + 'useLayoutEffect unmount',
215 + 'useEffect unmount',
216 + 'useLayoutEffect mount',
217 + 'useEffect mount',
218 + ]);
219 +
220 + ReactNoop.act(() => {
221 + ReactNoop.render(<App text={'update'} />);
222 + });
223 +
224 + expect(Scheduler).toHaveYielded([
225 + 'useLayoutEffect unmount',
226 + 'useLayoutEffect mount',
227 + 'useEffect unmount',
228 + 'useEffect mount',
229 + ]);
230 +
231 + ReactNoop.act(() => {
232 + ReactNoop.render(null);
233 + });
234 +
235 + expect(Scheduler).toHaveYielded([
236 + 'useLayoutEffect unmount',
237 + 'useEffect unmount',
238 + ]);
239 + });
240 +
241 + it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {
242 + function App({text}) {
243 + React.useEffect(() => {
244 + Scheduler.unstable_yieldValue('useEffect One mount');
245 + return () => Scheduler.unstable_yieldValue('useEffect One unmount');
246 + });
247 +
248 + React.useEffect(() => {
249 + Scheduler.unstable_yieldValue('useEffect Two mount');
250 + return () => Scheduler.unstable_yieldValue('useEffect Two unmount');
251 + });
252 +
253 + return text;
254 + }
255 +
256 + ReactNoop.act(() => {
257 + ReactNoop.render(<App text={'mount'} />);
258 + });
259 +
260 + expect(Scheduler).toHaveYielded([
261 + 'useEffect One mount',
262 + 'useEffect Two mount',
263 + 'useEffect One unmount',
264 + 'useEffect Two unmount',
265 + 'useEffect One mount',
266 + 'useEffect Two mount',
267 + ]);
268 +
269 + ReactNoop.act(() => {
270 + ReactNoop.render(<App text={'update'} />);
271 + });
272 +
273 + expect(Scheduler).toHaveYielded([
274 + 'useEffect One unmount',
275 + 'useEffect Two unmount',
276 + 'useEffect One mount',
277 + 'useEffect Two mount',
278 + ]);
279 +
280 + ReactNoop.act(() => {
281 + ReactNoop.render(null);
282 + });
283 +
284 + expect(Scheduler).toHaveYielded([
285 + 'useEffect One unmount',
286 + 'useEffect Two unmount',
287 + ]);
288 + });
289 +
290 + it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => {
291 + function App({text}) {
292 + React.useLayoutEffect(() => {
293 + Scheduler.unstable_yieldValue('useLayoutEffect One mount');
294 + return () =>
295 + Scheduler.unstable_yieldValue('useLayoutEffect One unmount');
296 + });
297 +
298 + React.useLayoutEffect(() => {
299 + Scheduler.unstable_yieldValue('useLayoutEffect Two mount');
300 + return () =>
301 + Scheduler.unstable_yieldValue('useLayoutEffect Two unmount');
302 + });
303 +
304 + return text;
305 + }
306 +
307 + ReactNoop.act(() => {
308 + ReactNoop.render(<App text={'mount'} />);
309 + });
310 +
311 + expect(Scheduler).toHaveYielded([
312 + 'useLayoutEffect One mount',
313 + 'useLayoutEffect Two mount',
314 + 'useLayoutEffect One unmount',
315 + 'useLayoutEffect Two unmount',
316 + 'useLayoutEffect One mount',
317 + 'useLayoutEffect Two mount',
318 + ]);
319 +
320 + ReactNoop.act(() => {
321 + ReactNoop.render(<App text={'update'} />);
322 + });
323 +
324 + expect(Scheduler).toHaveYielded([
325 + 'useLayoutEffect One unmount',
326 + 'useLayoutEffect Two unmount',
327 + 'useLayoutEffect One mount',
328 + 'useLayoutEffect Two mount',
329 + ]);
330 +
331 + ReactNoop.act(() => {
332 + ReactNoop.render(null);
333 + });
334 +
335 + expect(Scheduler).toHaveYielded([
336 + 'useLayoutEffect One unmount',
337 + 'useLayoutEffect Two unmount',
338 + ]);
339 + });
340 +
341 + it('useEffect and useLayoutEffect is called twice when there is no unmount', () => {
342 + function App({text}) {
343 + React.useEffect(() => {
344 + Scheduler.unstable_yieldValue('useEffect mount');
345 + });
346 +
347 + React.useLayoutEffect(() => {
348 + Scheduler.unstable_yieldValue('useLayoutEffect mount');
349 + });
350 +
351 + return text;
352 + }
353 +
354 + ReactNoop.act(() => {
355 + ReactNoop.render(<App text={'mount'} />);
356 + });
357 +
358 + expect(Scheduler).toHaveYielded([
359 + 'useLayoutEffect mount',
360 + 'useEffect mount',
361 + 'useLayoutEffect mount',
362 + 'useEffect mount',
363 + ]);
364 +
365 + ReactNoop.act(() => {
366 + ReactNoop.render(<App text={'update'} />);
367 + });
368 +
369 + expect(Scheduler).toHaveYielded([
370 + 'useLayoutEffect mount',
371 + 'useEffect mount',
372 + ]);
373 +
374 + ReactNoop.act(() => {
375 + ReactNoop.render(null);
376 + });
377 +
378 + expect(Scheduler).toHaveYielded([]);
379 + });
380 +
381 + it('passes the right context to class component lifecycles', () => {
382 + class App extends React.PureComponent {
383 + test() {}
384 +
385 + componentDidMount() {
386 + this.test();
387 + Scheduler.unstable_yieldValue('componentDidMount');
388 + }
389 +
390 + componentDidUpdate() {
391 + this.test();
392 + Scheduler.unstable_yieldValue('componentDidUpdate');
393 + }
394 +
395 + componentWillUnmount() {
396 + this.test();
397 + Scheduler.unstable_yieldValue('componentWillUnmount');
398 + }
399 +
400 + render() {
401 + return null;
402 + }
403 + }
404 +
405 + ReactNoop.act(() => {
406 + ReactNoop.render(<App />);
407 + });
408 +
409 + expect(Scheduler).toHaveYielded([
410 + 'componentDidMount',
411 + 'componentWillUnmount',
412 + 'componentDidMount',
413 + ]);
414 + });
415 +
416 + it('double invoking works for class components', () => {
417 + class App extends React.PureComponent {
418 + componentDidMount() {
419 + Scheduler.unstable_yieldValue('componentDidMount');
420 + }
421 +
422 + componentDidUpdate() {
423 + Scheduler.unstable_yieldValue('componentDidUpdate');
424 + }
425 +
426 + componentWillUnmount() {
427 + Scheduler.unstable_yieldValue('componentWillUnmount');
428 + }
429 +
430 + render() {
431 + return this.props.text;
432 + }
433 + }
434 +
435 + ReactNoop.act(() => {
436 + ReactNoop.render(<App text={'mount'} />);
437 + });
438 +
439 + expect(Scheduler).toHaveYielded([
440 + 'componentDidMount',
441 + 'componentWillUnmount',
442 + 'componentDidMount',
443 + ]);
444 +
445 + ReactNoop.act(() => {
446 + ReactNoop.render(<App text={'update'} />);
447 + });
448 +
449 + expect(Scheduler).toHaveYielded(['componentDidUpdate']);
450 +
451 + ReactNoop.act(() => {
452 + ReactNoop.render(null);
453 + });
454 +
455 + expect(Scheduler).toHaveYielded(['componentWillUnmount']);
456 + });
457 +
458 + it('double flushing passive effects only results in one double invoke', () => {
459 + function App({text}) {
460 + const [state, setState] = React.useState(0);
461 + React.useEffect(() => {
462 + if (state !== 1) {
463 + setState(1);
464 + }
465 + Scheduler.unstable_yieldValue('useEffect mount');
466 + return () => Scheduler.unstable_yieldValue('useEffect unmount');
467 + });
468 +
469 + React.useLayoutEffect(() => {
470 + Scheduler.unstable_yieldValue('useLayoutEffect mount');
471 + return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
472 + });
473 +
474 + Scheduler.unstable_yieldValue(text);
475 + return text;
476 + }
477 +
478 + ReactNoop.act(() => {
479 + ReactNoop.render(<App text={'mount'} />);
480 + });
481 +
482 + expect(Scheduler).toHaveYielded([
483 + 'mount',
484 + 'useLayoutEffect mount',
485 + 'useEffect mount',
486 + 'useLayoutEffect unmount',
487 + 'useEffect unmount',
488 + 'useLayoutEffect mount',
489 + 'useEffect mount',
490 + 'mount',
491 + 'useLayoutEffect unmount',
492 + 'useLayoutEffect mount',
493 + 'useEffect unmount',
494 + 'useEffect mount',
495 + ]);
496 + });
497 +
498 + it('newly mounted components after initial mount get double invoked', () => {
499 + let _setShowChild;
500 + function Child() {
501 + React.useEffect(() => {
502 + Scheduler.unstable_yieldValue('Child useEffect mount');
503 + return () => Scheduler.unstable_yieldValue('Child useEffect unmount');
504 + });
505 + React.useLayoutEffect(() => {
506 + Scheduler.unstable_yieldValue('Child useLayoutEffect mount');
507 + return () =>
508 + Scheduler.unstable_yieldValue('Child useLayoutEffect unmount');
509 + });
510 +
511 + return null;
512 + }
513 +
514 + function App() {
515 + const [showChild, setShowChild] = React.useState(false);
516 + _setShowChild = setShowChild;
517 + React.useEffect(() => {
518 + Scheduler.unstable_yieldValue('App useEffect mount');
519 + return () => Scheduler.unstable_yieldValue('App useEffect unmount');
520 + });
521 + React.useLayoutEffect(() => {
522 + Scheduler.unstable_yieldValue('App useLayoutEffect mount');
523 + return () =>
524 + Scheduler.unstable_yieldValue('App useLayoutEffect unmount');
525 + });
526 +
527 + return showChild && <Child />;
528 + }
529 +
530 + ReactNoop.act(() => {
531 + ReactNoop.render(<App />);
532 + });
533 +
534 + expect(Scheduler).toHaveYielded([
535 + 'App useLayoutEffect mount',
536 + 'App useEffect mount',
537 + 'App useLayoutEffect unmount',
538 + 'App useEffect unmount',
539 + 'App useLayoutEffect mount',
540 + 'App useEffect mount',
541 + ]);
542 +
543 + ReactNoop.act(() => {
544 + _setShowChild(true);
545 + });
546 +
547 + expect(Scheduler).toHaveYielded([
548 + 'App useLayoutEffect unmount',
549 + 'Child useLayoutEffect mount',
550 + 'App useLayoutEffect mount',
551 + 'App useEffect unmount',
552 + 'Child useEffect mount',
553 + 'App useEffect mount',
554 + 'Child useLayoutEffect unmount',
555 + 'Child useEffect unmount',
556 + 'Child useLayoutEffect mount',
557 + 'Child useEffect mount',
558 + ]);
559 + });
560 +
561 + it('classes and functions are double invoked together correctly', () => {
562 + class ClassChild extends React.PureComponent {
563 + componentDidMount() {
564 + Scheduler.unstable_yieldValue('componentDidMount');
565 + }
566 +
567 + componentWillUnmount() {
568 + Scheduler.unstable_yieldValue('componentWillUnmount');
569 + }
570 +
571 + render() {
572 + return this.props.text;
573 + }
574 + }
575 +
576 + function FunctionChild({text}) {
577 + React.useEffect(() => {
578 + Scheduler.unstable_yieldValue('useEffect mount');
579 + return () => Scheduler.unstable_yieldValue('useEffect unmount');
580 + });
581 + React.useLayoutEffect(() => {
582 + Scheduler.unstable_yieldValue('useLayoutEffect mount');
583 + return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount');
584 + });
585 + return text;
586 + }
587 +
588 + function App({text}) {
589 + return (
590 + <>
591 + <ClassChild text={text} />
592 + <FunctionChild text={text} />
593 + </>
594 + );
595 + }
596 +
597 + ReactNoop.act(() => {
598 + ReactNoop.render(<App text={'mount'} />);
599 + });
600 +
601 + expect(Scheduler).toHaveYielded([
602 + 'componentDidMount',
603 + 'useLayoutEffect mount',
604 + 'useEffect mount',
605 + 'componentWillUnmount',
606 + 'useLayoutEffect unmount',
607 + 'useEffect unmount',
608 + 'componentDidMount',
609 + 'useLayoutEffect mount',
610 + 'useEffect mount',
611 + ]);
612 +
613 + ReactNoop.act(() => {
614 + ReactNoop.render(<App text={'mount'} />);
615 + });
616 +
617 + expect(Scheduler).toHaveYielded([
618 + 'useLayoutEffect unmount',
619 + 'useLayoutEffect mount',
620 + 'useEffect unmount',
621 + 'useEffect mount',
622 + ]);
623 +
624 + ReactNoop.act(() => {
625 + ReactNoop.render(null);
626 + });
627 +
628 + expect(Scheduler).toHaveYielded([
629 + 'componentWillUnmount',
630 + 'useLayoutEffect unmount',
631 + 'useEffect unmount',
632 + ]);
633 + });
634 + }
635 +});
packages/react-test-renderer/src/ReactTestRenderer.js
+1
@@ -451,6 +451,7 @@ function create(element: React$Element<any>, options: TestRendererOptions) {
451 isConcurrent ? ConcurrentRoot : LegacyRoot,
452 false,
453 null,
454 + null,
455 );
456 invariant(root != null, 'something went wrong');
457 updateContainer(element, root, null, null);
packages/react/src/__tests__/ReactProfiler-test.internal.js
+2 -1
@@ -4875,7 +4875,8 @@ describe('Profiler', () => {
4875
4876 if (__DEV__) {
4877 it('double invoking does not disconnect wrapped async work', () => {
4878 - ReactFeatureFlags.enableDoubleInvokingEffects = true;
4878 + ReactFeatureFlags.enableStrictEffects = true;
4879 + ReactFeatureFlags.createRootStrictEffectsByDefault = true;
4880
4881 const callback = jest.fn(() => {
4882 const wrappedInteractions = SchedulerTracing.unstable_getCurrent();
packages/react/src/__tests__/ReactStrictMode-test.internal.js new
+264
@@ -0,0 +1,264 @@
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 +describe('ReactStrictMode', () => {
13 + let React;
14 + let ReactDOM;
15 + let act;
16 +
17 + beforeEach(() => {
18 + jest.resetModules();
19 + React = require('react');
20 + ReactDOM = require('react-dom');
21 +
22 + const TestUtils = require('react-dom/test-utils');
23 + act = TestUtils.unstable_concurrentAct;
24 +
25 + const ReactFeatureFlags = require('shared/ReactFeatureFlags');
26 + ReactFeatureFlags.enableStrictEffects = __DEV__;
27 + });
28 +
29 + describe('levels', () => {
30 + let log;
31 +
32 + beforeEach(() => {
33 + log = [];
34 + });
35 +
36 + function Component({label}) {
37 + React.useEffect(() => {
38 + log.push(`${label}: useEffect mount`);
39 + return () => log.push(`${label}: useEffect unmount`);
40 + });
41 +
42 + React.useLayoutEffect(() => {
43 + log.push(`${label}: useLayoutEffect mount`);
44 + return () => log.push(`${label}: useLayoutEffect unmount`);
45 + });
46 +
47 + log.push(`${label}: render`);
48 +
49 + return null;
50 + }
51 +
52 + // @gate experimental
53 + it('should support overriding default via createRoot option', () => {
54 + act(() => {
55 + const container = document.createElement('div');
56 + const root = ReactDOM.createRoot(container, {
57 + unstable_strictModeLevel: 0,
58 + });
59 + root.render(<Component label="A" />);
60 + });
61 +
62 + expect(log).toEqual([
63 + 'A: render',
64 + 'A: useLayoutEffect mount',
65 + 'A: useEffect mount',
66 + ]);
67 + });
68 +
69 + // @gate experimental
70 + it('should support overriding default via createBlockingRoot option', () => {
71 + act(() => {
72 + const container = document.createElement('div');
73 + const root = ReactDOM.createBlockingRoot(container, {
74 + unstable_strictModeLevel: 0,
75 + });
76 + root.render(<Component label="A" />);
77 + });
78 +
79 + expect(log).toEqual([
80 + 'A: render',
81 + 'A: useLayoutEffect mount',
82 + 'A: useEffect mount',
83 + ]);
84 + });
85 +
86 + // @gate experimental
87 + it('should disable strict mode if level 0 is specified', () => {
88 + act(() => {
89 + const container = document.createElement('div');
90 + const root = ReactDOM.createRoot(container, {
91 + unstable_strictModeLevel: 0,
92 + });
93 + root.render(
94 + <React.StrictMode unstable_level={0}>
95 + <Component label="A" />
96 + </React.StrictMode>,
97 + );
98 + });
99 +
100 + expect(log).toEqual([
101 + 'A: render',
102 + 'A: useLayoutEffect mount',
103 + 'A: useEffect mount',
104 + ]);
105 + });
106 +
107 + if (__DEV__) {
108 + // @gate experimental
109 + it('should default to level 1 (legacy mode)', () => {
110 + act(() => {
111 + const container = document.createElement('div');
112 + const root = ReactDOM.createRoot(container);
113 + root.render(
114 + <React.StrictMode>
115 + <Component label="A" />
116 + </React.StrictMode>,
117 + );
118 + });
119 +
120 + expect(log).toEqual([
121 + 'A: render',
122 + 'A: render',
123 + 'A: useLayoutEffect mount',
124 + 'A: useEffect mount',
125 + ]);
126 + });
127 +
128 + // @gate experimental
129 + it('should support level 1 (legacy mode)', () => {
130 + act(() => {
131 + const container = document.createElement('div');
132 + const root = ReactDOM.createRoot(container);
133 + root.render(
134 + <React.StrictMode unstable_level={1}>
135 + <Component label="A" />
136 + </React.StrictMode>,
137 + );
138 + });
139 +
140 + expect(log).toEqual([
141 + 'A: render',
142 + 'A: render',
143 + 'A: useLayoutEffect mount',
144 + 'A: useEffect mount',
145 + ]);
146 + });
147 +
148 + // @gate experimental
149 + it('should support level 2 (legacy + strict effects mode)', () => {
150 + act(() => {
151 + const container = document.createElement('div');
152 + const root = ReactDOM.createRoot(container);
153 + root.render(
154 + <React.StrictMode unstable_level={2}>
155 + <Component label="A" />
156 + </React.StrictMode>,
157 + );
158 + });
159 +
160 + expect(log).toEqual([
161 + 'A: render',
162 + 'A: render',
163 + 'A: useLayoutEffect mount',
164 + 'A: useEffect mount',
165 + 'A: useLayoutEffect unmount',
166 + 'A: useEffect unmount',
167 + 'A: useLayoutEffect mount',
168 + 'A: useEffect mount',
169 + ]);
170 + });
171 +
172 + // @gate experimental
173 + it('should allow level to be increased with nesting', () => {
174 + act(() => {
175 + const container = document.createElement('div');
176 + const root = ReactDOM.createRoot(container, {
177 + unstable_strictModeLevel: 0,
178 + });
179 + root.render(
180 + <>
181 + <Component label="A" />
182 + <React.StrictMode unstable_level={1}>
183 + <Component label="B" />
184 + <React.StrictMode unstable_level={2}>
185 + <Component label="C" />
186 + </React.StrictMode>
187 + ,
188 + </React.StrictMode>
189 + ,
190 + </>,
191 + );
192 + });
193 +
194 + expect(log).toEqual([
195 + 'A: render',
196 + 'B: render',
197 + 'B: render',
198 + 'C: render',
199 + 'C: render',
200 + 'A: useLayoutEffect mount',
201 + 'B: useLayoutEffect mount',
202 + 'C: useLayoutEffect mount',
203 + 'A: useEffect mount',
204 + 'B: useEffect mount',
205 + 'C: useEffect mount',
206 + 'C: useLayoutEffect unmount',
207 + 'C: useEffect unmount',
208 + 'C: useLayoutEffect mount',
209 + 'C: useEffect mount',
210 + ]);
211 + });
212 +
213 + // @gate experimental
214 + it('should not allow level to be decreased with nesting', () => {
215 + act(() => {
216 + const container = document.createElement('div');
217 + const root = ReactDOM.createRoot(container, {
218 + unstable_strictModeLevel: 2,
219 + });
220 + root.render(
221 + <>
222 + <Component label="A" />
223 + <React.StrictMode unstable_level={1}>
224 + <Component label="B" />
225 + <React.StrictMode unstable_level={0}>
226 + <Component label="C" />
227 + </React.StrictMode>
228 + ,
229 + </React.StrictMode>
230 + ,
231 + </>,
232 + );
233 + });
234 +
235 + expect(log).toEqual([
236 + 'A: render',
237 + 'A: render',
238 + 'B: render',
239 + 'B: render',
240 + 'C: render',
241 + 'C: render',
242 + 'A: useLayoutEffect mount',
243 + 'B: useLayoutEffect mount',
244 + 'C: useLayoutEffect mount',
245 + 'A: useEffect mount',
246 + 'B: useEffect mount',
247 + 'C: useEffect mount',
248 + 'A: useLayoutEffect unmount',
249 + 'B: useLayoutEffect unmount',
250 + 'C: useLayoutEffect unmount',
251 + 'A: useEffect unmount',
252 + 'B: useEffect unmount',
253 + 'C: useEffect unmount',
254 + 'A: useLayoutEffect mount',
255 + 'B: useLayoutEffect mount',
256 + 'C: useLayoutEffect mount',
257 + 'A: useEffect mount',
258 + 'B: useEffect mount',
259 + 'C: useEffect mount',
260 + ]);
261 + });
262 + }
263 + });
264 +});
packages/shared/ReactFeatureFlags.js
+9 -3
@@ -20,9 +20,17 @@ export const enableDebugTracing = false;
20 export const enableSchedulingProfiler = __PROFILE__ && __EXPERIMENTAL__;
21
22 // Helps identify side effects in render-phase lifecycle hooks and setState
23 -// reducers by double invoking them in Strict Mode.
23 +// reducers by double invoking them in StrictLegacyMode.
24 export const debugRenderPhaseSideEffectsForStrictMode = __DEV__;
25
26 +// Helps identify code that is not safe for planned Offscreen API and Suspense semantics;
27 +// this feature flag only impacts StrictEffectsMode.
28 +export const enableStrictEffects = false;
29 +
30 +// If TRUE, trees rendered with createRoot (and createBlockingRoot) APIs will be StrictEffectsMode.
31 +// If FALSE, these trees will be StrictLegacyMode.
32 +export const createRootStrictEffectsByDefault = false;
33 +
34 // To preserve the "Pause on caught exceptions" behavior of the debugger, we
35 // replay the begin phase of a failed component inside invokeGuardedCallback.
36 export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__;
@@ -139,8 +147,6 @@ export const decoupleUpdatePriorityFromScheduler = false;
147
148 export const enableDiscreteEventFlushingChange = false;
149
142 -export const enableDoubleInvokingEffects = false;
143 -
150 export const enableUseRefAccessWarning = false;
151
152 export const enableRecursiveCommitTraversal = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+2 -1
@@ -52,7 +52,8 @@ export const deferRenderPhaseUpdateToNextBatch = true;
52 export const decoupleUpdatePriorityFromScheduler = false;
53 export const enableDiscreteEventFlushingChange = false;
54
55 -export const enableDoubleInvokingEffects = false;
55 +export const enableStrictEffects = false;
56 +export const createRootStrictEffectsByDefault = false;
57 export const enableUseRefAccessWarning = false;
58
59 export const enableRecursiveCommitTraversal = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+2 -1
@@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true;
51 export const decoupleUpdatePriorityFromScheduler = false;
52 export const enableDiscreteEventFlushingChange = false;
53
54 -export const enableDoubleInvokingEffects = false;
54 +export const enableStrictEffects = false;
55 +export const createRootStrictEffectsByDefault = false;
56 export const enableUseRefAccessWarning = false;
57
58 export const enableRecursiveCommitTraversal = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+2 -1
@@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true;
51 export const decoupleUpdatePriorityFromScheduler = false;
52 export const enableDiscreteEventFlushingChange = false;
53
54 -export const enableDoubleInvokingEffects = false;
54 +export const enableStrictEffects = false;
55 +export const createRootStrictEffectsByDefault = false;
56 export const enableUseRefAccessWarning = false;
57
58 export const enableRecursiveCommitTraversal = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+2 -1
@@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true;
51 export const decoupleUpdatePriorityFromScheduler = false;
52 export const enableDiscreteEventFlushingChange = false;
53
54 -export const enableDoubleInvokingEffects = false;
54 +export const enableStrictEffects = false;
55 +export const createRootStrictEffectsByDefault = false;
56 export const enableUseRefAccessWarning = false;
57
58 export const enableRecursiveCommitTraversal = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+2 -1
@@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true;
51 export const decoupleUpdatePriorityFromScheduler = false;
52 export const enableDiscreteEventFlushingChange = false;
53
54 -export const enableDoubleInvokingEffects = true;
54 +export const enableStrictEffects = true;
55 +export const createRootStrictEffectsByDefault = false;
56 export const enableUseRefAccessWarning = false;
57
58 export const enableRecursiveCommitTraversal = false;
packages/shared/forks/ReactFeatureFlags.testing.js
+2 -1
@@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true;
51 export const decoupleUpdatePriorityFromScheduler = false;
52 export const enableDiscreteEventFlushingChange = false;
53
54 -export const enableDoubleInvokingEffects = false;
54 +export const enableStrictEffects = false;
55 +export const createRootStrictEffectsByDefault = false;
56 export const enableUseRefAccessWarning = false;
57
58 export const enableRecursiveCommitTraversal = false;
packages/shared/forks/ReactFeatureFlags.testing.www.js
+2 -1
@@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true;
51 export const decoupleUpdatePriorityFromScheduler = false;
52 export const enableDiscreteEventFlushingChange = true;
53
54 -export const enableDoubleInvokingEffects = false;
54 +export const enableStrictEffects = false;
55 +export const createRootStrictEffectsByDefault = false;
56 export const enableUseRefAccessWarning = false;
57
58 export const enableRecursiveCommitTraversal = false;
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+2 -1
@@ -50,7 +50,8 @@ export const enableTrustedTypesIntegration = false;
50 export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
51 export const disableNativeComponentFrames = false;
52
53 -export const enableDoubleInvokingEffects = false;
53 +export const createRootStrictEffectsByDefault = false;
54 +export const enableStrictEffects = false;
55 export const enableUseRefAccessWarning = __VARIANT__;
56
57 export const enableProfilerNestedUpdateScheduledHook = __VARIANT__;
packages/shared/forks/ReactFeatureFlags.www.js
+2 -1
@@ -27,7 +27,8 @@ export const {
27 decoupleUpdatePriorityFromScheduler,
28 enableDebugTracing,
29 skipUnmountedBoundaries,
30 - enableDoubleInvokingEffects,
30 + enableStrictEffects,
31 + createRootStrictEffectsByDefault,
32 enableUseRefAccessWarning,
33 disableNativeComponentFrames,
34 disableSchedulerTimeoutInWorkLoop,