Skip to content

Commit 5127353

Browse files
author
Brian Vaughn
committed
Add StrictMode 'level' prop and createRoot 'strictModeLevel' option
New StrictMode '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 createRoot option ('unstable_strictModeLevel') 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 d79faf2 commit 5127353

File tree

12 files changed

+350
-30
lines changed

12 files changed

+350
-30
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-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: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -421,14 +421,29 @@ 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+
432+
if (strictModeLevelOverride !== null) {
433+
if (strictModeLevelOverride >= 1) {
434+
mode |= StrictLegacyMode;
435+
}
436+
if (enableStrictEffects) {
437+
if (strictModeLevelOverride >= 2) {
438+
mode |= StrictEffectsMode;
439+
}
440+
}
430441
} else {
431-
mode = ConcurrentMode | BlockingMode | StrictLegacyMode;
442+
if (enableStrictEffects && createRootStrictEffectsByDefault) {
443+
mode |= StrictLegacyMode | StrictEffectsMode;
444+
} else {
445+
mode |= StrictLegacyMode;
446+
}
432447
}
433448
} else if (tag === BlockingRoot) {
434449
if (enableStrictEffects && createRootStrictEffectsByDefault) {
@@ -484,8 +499,20 @@ export function createFiberFromTypeAndProps(
484499
break;
485500
case REACT_STRICT_MODE_TYPE:
486501
fiberTag = Mode;
487-
// TODO (StrictEffectsMode) Add support for new strict mode "level" attribute
488-
mode |= StrictLegacyMode;
502+
503+
// Legacy strict mode (<StrictMode> without any level prop) defaults to level 1.
504+
const level = pendingProps.level == null ? 1 : pendingProps.level;
505+
506+
// Levels cascade; higher levels inherit all lower level modes.
507+
// It is explicitly not supported to lower a mode with nesting, only to increase it.
508+
if (level >= 1) {
509+
mode |= StrictLegacyMode;
510+
}
511+
if (enableStrictEffects) {
512+
if (level >= 2) {
513+
mode |= StrictEffectsMode;
514+
}
515+
}
489516
break;
490517
case REACT_PROFILER_TYPE:
491518
return createFiberFromProfiler(pendingProps, mode, lanes, key);

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

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -421,14 +421,29 @@ 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+
432+
if (strictModeLevelOverride !== null) {
433+
if (strictModeLevelOverride >= 1) {
434+
mode |= StrictLegacyMode;
435+
}
436+
if (enableStrictEffects) {
437+
if (strictModeLevelOverride >= 2) {
438+
mode |= StrictEffectsMode;
439+
}
440+
}
430441
} else {
431-
mode = ConcurrentMode | BlockingMode | StrictLegacyMode;
442+
if (enableStrictEffects && createRootStrictEffectsByDefault) {
443+
mode |= StrictLegacyMode | StrictEffectsMode;
444+
} else {
445+
mode |= StrictLegacyMode;
446+
}
432447
}
433448
} else if (tag === BlockingRoot) {
434449
if (enableStrictEffects && createRootStrictEffectsByDefault) {
@@ -484,8 +499,20 @@ export function createFiberFromTypeAndProps(
484499
break;
485500
case REACT_STRICT_MODE_TYPE:
486501
fiberTag = Mode;
487-
// TODO (StrictEffectsMode) Add support for new strict mode "level" attribute
488-
mode |= StrictLegacyMode;
502+
503+
// Legacy strict mode (<StrictMode> without any level prop) defaults to level 1.
504+
const level = pendingProps.level == null ? 1 : pendingProps.level;
505+
506+
// Levels cascade; higher levels inherit all lower level modes.
507+
// It is explicitly not supported to lower a mode with nesting, only to increase it.
508+
if (level >= 1) {
509+
mode |= StrictLegacyMode;
510+
}
511+
if (enableStrictEffects) {
512+
if (level >= 2) {
513+
mode |= StrictEffectsMode;
514+
}
515+
}
489516
break;
490517
case REACT_PROFILER_TYPE:
491518
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)