Skip to content

Commit 9209c30

Browse files
author
Brian Vaughn
authored
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
1 parent 4190a34 commit 9209c30

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1243
-934
lines changed

packages/react-dom/src/client/ReactDOMRoot.js

+13-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export type RootOptions = {
2727
mutableSources?: Array<MutableSource<any>>,
2828
...
2929
},
30+
unstable_strictModeLevel?: number,
3031
...
3132
};
3233

@@ -128,7 +129,18 @@ function createRootImpl(
128129
options.hydrationOptions != null &&
129130
options.hydrationOptions.mutableSources) ||
130131
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+
);
132144
markContainerAsRoot(root.current, container);
133145

134146
const rootContainerElement =

packages/react-native-renderer/src/ReactFabric.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ function render(
203203
if (!root) {
204204
// TODO (bvaughn): If we decide to keep the wrapper component,
205205
// 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);
207207
roots.set(containerTag, root);
208208
}
209209
updateContainer(element, root, null, callback);

packages/react-native-renderer/src/ReactNativeRenderer.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ function render(
202202
if (!root) {
203203
// TODO (bvaughn): If we decide to keep the wrapper component,
204204
// 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);
206206
roots.set(containerTag, root);
207207
}
208208
updateContainer(element, root, null, callback);

packages/react-noop-renderer/src/createReactNoop.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
722722
if (!root) {
723723
const container = {rootID: rootID, pendingChildren: [], children: []};
724724
rootContainers.set(rootID, container);
725-
root = NoopRenderer.createContainer(container, tag, false, null);
725+
root = NoopRenderer.createContainer(container, tag, false, null, null);
726726
roots.set(rootID, root);
727727
}
728728
return root.current.stateNode.containerInfo;
@@ -740,6 +740,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
740740
ConcurrentRoot,
741741
false,
742742
null,
743+
null,
743744
);
744745
return {
745746
_Scheduler: Scheduler,
@@ -766,6 +767,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
766767
BlockingRoot,
767768
false,
768769
null,
770+
null,
769771
);
770772
return {
771773
_Scheduler: Scheduler,
@@ -792,6 +794,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
792794
LegacyRoot,
793795
false,
794796
null,
797+
null,
795798
);
796799
return {
797800
_Scheduler: Scheduler,

packages/react-reconciler/src/ReactChildFiber.new.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
} from './ReactFiber.new';
4747
import {emptyRefsObject} from './ReactFiberClassComponent.new';
4848
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.new';
49-
import {StrictMode} from './ReactTypeOfMode';
49+
import {StrictLegacyMode} from './ReactTypeOfMode';
5050

5151
let didWarnAboutMaps;
5252
let didWarnAboutGenerators;
@@ -114,7 +114,7 @@ function coerceRef(
114114
// TODO: Clean this up once we turn on the string ref warning for
115115
// everyone, because the strict mode case will no longer be relevant
116116
if (
117-
(returnFiber.mode & StrictMode || warnAboutStringRefs) &&
117+
(returnFiber.mode & StrictLegacyMode || warnAboutStringRefs) &&
118118
// We warn in ReactElement.js if owner and self are equal for string refs
119119
// because these cannot be automatically converted to an arrow function
120120
// using a codemod. Therefore, we don't have to warn about string refs again.

packages/react-reconciler/src/ReactChildFiber.old.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import {
4646
} from './ReactFiber.old';
4747
import {emptyRefsObject} from './ReactFiberClassComponent.old';
4848
import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.old';
49-
import {StrictMode} from './ReactTypeOfMode';
49+
import {StrictLegacyMode} from './ReactTypeOfMode';
5050

5151
let didWarnAboutMaps;
5252
let didWarnAboutGenerators;
@@ -114,7 +114,7 @@ function coerceRef(
114114
// TODO: Clean this up once we turn on the string ref warning for
115115
// everyone, because the strict mode case will no longer be relevant
116116
if (
117-
(returnFiber.mode & StrictMode || warnAboutStringRefs) &&
117+
(returnFiber.mode & StrictLegacyMode || warnAboutStringRefs) &&
118118
// We warn in ReactElement.js if owner and self are equal for string refs
119119
// because these cannot be automatically converted to an arrow function
120120
// using a codemod. Therefore, we don't have to warn about string refs again.

packages/react-reconciler/src/ReactFiber.new.js

+58-6
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ import type {OffscreenProps} from './ReactFiberOffscreenComponent';
1919

2020
import invariant from 'shared/invariant';
2121
import {
22+
createRootStrictEffectsByDefault,
23+
enableCache,
24+
enableStrictEffects,
2225
enableProfilerTimer,
2326
enableScopeAPI,
24-
enableCache,
2527
} from 'shared/ReactFeatureFlags';
2628
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
2729
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
@@ -64,7 +66,8 @@ import {
6466
ConcurrentMode,
6567
DebugTracingMode,
6668
ProfileMode,
67-
StrictMode,
69+
StrictLegacyMode,
70+
StrictEffectsMode,
6871
BlockingMode,
6972
} from './ReactTypeOfMode';
7073
import {
@@ -418,12 +421,47 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
418421
return workInProgress;
419422
}
420423

421-
export function createHostRootFiber(tag: RootTag): Fiber {
424+
export function createHostRootFiber(
425+
tag: RootTag,
426+
strictModeLevelOverride: null | number,
427+
): Fiber {
422428
let mode;
423429
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+
}
425447
} 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+
}
427465
} else {
428466
mode = NoMode;
429467
}
@@ -472,7 +510,21 @@ export function createFiberFromTypeAndProps(
472510
break;
473511
case REACT_STRICT_MODE_TYPE:
474512
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+
}
476528
break;
477529
case REACT_PROFILER_TYPE:
478530
return createFiberFromProfiler(pendingProps, mode, lanes, key);

packages/react-reconciler/src/ReactFiber.old.js

+58-6
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@ import type {OffscreenProps} from './ReactFiberOffscreenComponent';
1919

2020
import invariant from 'shared/invariant';
2121
import {
22+
createRootStrictEffectsByDefault,
23+
enableCache,
24+
enableStrictEffects,
2225
enableProfilerTimer,
2326
enableScopeAPI,
24-
enableCache,
2527
} from 'shared/ReactFeatureFlags';
2628
import {NoFlags, Placement, StaticMask} from './ReactFiberFlags';
2729
import {ConcurrentRoot, BlockingRoot} from './ReactRootTags';
@@ -64,7 +66,8 @@ import {
6466
ConcurrentMode,
6567
DebugTracingMode,
6668
ProfileMode,
67-
StrictMode,
69+
StrictLegacyMode,
70+
StrictEffectsMode,
6871
BlockingMode,
6972
} from './ReactTypeOfMode';
7073
import {
@@ -418,12 +421,47 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
418421
return workInProgress;
419422
}
420423

421-
export function createHostRootFiber(tag: RootTag): Fiber {
424+
export function createHostRootFiber(
425+
tag: RootTag,
426+
strictModeLevelOverride: null | number,
427+
): Fiber {
422428
let mode;
423429
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+
}
425447
} 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+
}
427465
} else {
428466
mode = NoMode;
429467
}
@@ -472,7 +510,21 @@ export function createFiberFromTypeAndProps(
472510
break;
473511
case REACT_STRICT_MODE_TYPE:
474512
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+
}
476528
break;
477529
case REACT_PROFILER_TYPE:
478530
return createFiberFromProfiler(pendingProps, mode, lanes, key);

packages/react-reconciler/src/ReactFiberBeginWork.new.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ import {
125125
ConcurrentMode,
126126
NoMode,
127127
ProfileMode,
128-
StrictMode,
128+
StrictLegacyMode,
129129
BlockingMode,
130130
} from './ReactTypeOfMode';
131131
import {
@@ -357,7 +357,7 @@ function updateForwardRef(
357357
);
358358
if (
359359
debugRenderPhaseSideEffectsForStrictMode &&
360-
workInProgress.mode & StrictMode
360+
workInProgress.mode & StrictLegacyMode
361361
) {
362362
disableLogs();
363363
try {
@@ -889,7 +889,7 @@ function updateFunctionComponent(
889889
);
890890
if (
891891
debugRenderPhaseSideEffectsForStrictMode &&
892-
workInProgress.mode & StrictMode
892+
workInProgress.mode & StrictLegacyMode
893893
) {
894894
disableLogs();
895895
try {
@@ -1068,7 +1068,7 @@ function finishClassComponent(
10681068
nextChildren = instance.render();
10691069
if (
10701070
debugRenderPhaseSideEffectsForStrictMode &&
1071-
workInProgress.mode & StrictMode
1071+
workInProgress.mode & StrictLegacyMode
10721072
) {
10731073
disableLogs();
10741074
try {
@@ -1478,7 +1478,7 @@ function mountIndeterminateComponent(
14781478
}
14791479
}
14801480

1481-
if (workInProgress.mode & StrictMode) {
1481+
if (workInProgress.mode & StrictLegacyMode) {
14821482
ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null);
14831483
}
14841484

@@ -1615,7 +1615,7 @@ function mountIndeterminateComponent(
16151615

16161616
if (
16171617
debugRenderPhaseSideEffectsForStrictMode &&
1618-
workInProgress.mode & StrictMode
1618+
workInProgress.mode & StrictLegacyMode
16191619
) {
16201620
disableLogs();
16211621
try {

0 commit comments

Comments
 (0)