Skip to content

Commit 719b02f

Browse files
author
Brian Vaughn
committed
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 2431a51 commit 719b02f

File tree

14 files changed

+408
-38
lines changed

14 files changed

+408
-38
lines changed

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

Lines changed: 13 additions & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 4 additions & 1 deletion
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/ReactFiber.new.js

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -421,20 +421,46 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
421421
return workInProgress;
422422
}
423423

424-
export function createHostRootFiber(tag: RootTag): Fiber {
424+
export function createHostRootFiber(
425+
tag: RootTag,
426+
strictModeLevelOverride: null | number,
427+
): Fiber {
425428
let mode;
426429
if (tag === ConcurrentRoot) {
427-
if (enableStrictEffects && createRootStrictEffectsByDefault) {
428-
mode =
429-
ConcurrentMode | BlockingMode | StrictLegacyMode | StrictEffectsMode;
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+
}
430440
} else {
431-
mode = ConcurrentMode | BlockingMode | StrictLegacyMode;
441+
if (enableStrictEffects && createRootStrictEffectsByDefault) {
442+
mode |= StrictLegacyMode | StrictEffectsMode;
443+
} else {
444+
mode |= StrictLegacyMode;
445+
}
432446
}
433447
} else if (tag === BlockingRoot) {
434-
if (enableStrictEffects && createRootStrictEffectsByDefault) {
435-
mode = BlockingMode | StrictLegacyMode | StrictEffectsMode;
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+
}
436458
} else {
437-
mode = BlockingMode | StrictLegacyMode;
459+
if (enableStrictEffects && createRootStrictEffectsByDefault) {
460+
mode |= StrictLegacyMode | StrictEffectsMode;
461+
} else {
462+
mode |= StrictLegacyMode;
463+
}
438464
}
439465
} else {
440466
mode = NoMode;
@@ -484,8 +510,21 @@ export function createFiberFromTypeAndProps(
484510
break;
485511
case REACT_STRICT_MODE_TYPE:
486512
fiberTag = Mode;
487-
// TODO (StrictEffectsMode) Add support for new strict mode "level" attribute
488-
mode |= StrictLegacyMode;
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+
}
489528
break;
490529
case REACT_PROFILER_TYPE:
491530
return createFiberFromProfiler(pendingProps, mode, lanes, key);

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

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -421,20 +421,46 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
421421
return workInProgress;
422422
}
423423

424-
export function createHostRootFiber(tag: RootTag): Fiber {
424+
export function createHostRootFiber(
425+
tag: RootTag,
426+
strictModeLevelOverride: null | number,
427+
): Fiber {
425428
let mode;
426429
if (tag === ConcurrentRoot) {
427-
if (enableStrictEffects && createRootStrictEffectsByDefault) {
428-
mode =
429-
ConcurrentMode | BlockingMode | StrictLegacyMode | StrictEffectsMode;
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+
}
430440
} else {
431-
mode = ConcurrentMode | BlockingMode | StrictLegacyMode;
441+
if (enableStrictEffects && createRootStrictEffectsByDefault) {
442+
mode |= StrictLegacyMode | StrictEffectsMode;
443+
} else {
444+
mode |= StrictLegacyMode;
445+
}
432446
}
433447
} else if (tag === BlockingRoot) {
434-
if (enableStrictEffects && createRootStrictEffectsByDefault) {
435-
mode = BlockingMode | StrictLegacyMode | StrictEffectsMode;
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+
}
436458
} else {
437-
mode = BlockingMode | StrictLegacyMode;
459+
if (enableStrictEffects && createRootStrictEffectsByDefault) {
460+
mode |= StrictLegacyMode | StrictEffectsMode;
461+
} else {
462+
mode |= StrictLegacyMode;
463+
}
438464
}
439465
} else {
440466
mode = NoMode;
@@ -484,8 +510,21 @@ export function createFiberFromTypeAndProps(
484510
break;
485511
case REACT_STRICT_MODE_TYPE:
486512
fiberTag = Mode;
487-
// TODO (StrictEffectsMode) Add support for new strict mode "level" attribute
488-
mode |= StrictLegacyMode;
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+
}
489528
break;
490529
case REACT_PROFILER_TYPE:
491530
return createFiberFromProfiler(pendingProps, mode, lanes, key);

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,15 @@ export function createContainer(
256256
tag: RootTag,
257257
hydrate: boolean,
258258
hydrationCallbacks: null | SuspenseHydrationCallbacks,
259+
strictModeLevelOverride: null | number,
259260
): OpaqueRoot {
260-
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
261+
return createFiberRoot(
262+
containerInfo,
263+
tag,
264+
hydrate,
265+
hydrationCallbacks,
266+
strictModeLevelOverride,
267+
);
261268
}
262269

263270
export function updateContainer(

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,15 @@ export function createContainer(
256256
tag: RootTag,
257257
hydrate: boolean,
258258
hydrationCallbacks: null | SuspenseHydrationCallbacks,
259+
strictModeLevelOverride: null | number,
259260
): OpaqueRoot {
260-
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
261+
return createFiberRoot(
262+
containerInfo,
263+
tag,
264+
hydrate,
265+
hydrationCallbacks,
266+
strictModeLevelOverride,
267+
);
261268
}
262269

263270
export function updateContainer(

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export function createFiberRoot(
9191
tag: RootTag,
9292
hydrate: boolean,
9393
hydrationCallbacks: null | SuspenseHydrationCallbacks,
94+
strictModeLevelOverride: null | number,
9495
): FiberRoot {
9596
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
9697
if (enableSuspenseCallback) {
@@ -99,7 +100,7 @@ export function createFiberRoot(
99100

100101
// Cyclic construction. This cheats the type system right now because
101102
// stateNode is any.
102-
const uninitializedFiber = createHostRootFiber(tag);
103+
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
103104
root.current = uninitializedFiber;
104105
uninitializedFiber.stateNode = root;
105106

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ export function createFiberRoot(
9191
tag: RootTag,
9292
hydrate: boolean,
9393
hydrationCallbacks: null | SuspenseHydrationCallbacks,
94+
strictModeLevelOverride: null | number,
9495
): FiberRoot {
9596
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
9697
if (enableSuspenseCallback) {
@@ -99,7 +100,7 @@ export function createFiberRoot(
99100

100101
// Cyclic construction. This cheats the type system right now because
101102
// stateNode is any.
102-
const uninitializedFiber = createHostRootFiber(tag);
103+
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
103104
root.current = uninitializedFiber;
104105
uninitializedFiber.stateNode = root;
105106

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ import {
105105
import {
106106
NoMode,
107107
StrictLegacyMode,
108-
StrictEffectsMode,
109108
ProfileMode,
110109
BlockingMode,
111110
ConcurrentMode,
@@ -2562,10 +2561,9 @@ function commitDoubleInvokeEffectsInDEV(
25622561
hasPassiveEffects: boolean,
25632562
) {
25642563
if (__DEV__ && enableStrictEffects) {
2565-
// Never double-invoke effects outside of StrictEffectsMode.
2566-
if ((fiber.mode & StrictEffectsMode) === NoMode) {
2567-
return;
2568-
}
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.
25692567

25702568
setCurrentDebugFiberInDEV(fiber);
25712569
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ import {
105105
import {
106106
NoMode,
107107
StrictLegacyMode,
108-
StrictEffectsMode,
109108
ProfileMode,
110109
BlockingMode,
111110
ConcurrentMode,
@@ -2562,10 +2561,9 @@ function commitDoubleInvokeEffectsInDEV(
25622561
hasPassiveEffects: boolean,
25632562
) {
25642563
if (__DEV__ && enableStrictEffects) {
2565-
// Never double-invoke effects outside of StrictEffectsMode.
2566-
if ((fiber.mode & StrictEffectsMode) === NoMode) {
2567-
return;
2568-
}
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.
25692567

25702568
setCurrentDebugFiberInDEV(fiber);
25712569
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);

packages/react-test-renderer/src/ReactTestRenderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ function create(element: React$Element<any>, options: TestRendererOptions) {
451451
isConcurrent ? ConcurrentRoot : LegacyRoot,
452452
false,
453453
null,
454+
null,
454455
);
455456
invariant(root != null, 'something went wrong');
456457
updateContainer(element, root, null, null);

0 commit comments

Comments
 (0)