Skip to content

Commit

Permalink
Add StrictMode 'level' prop and createRoot 'strictModeLevel' option
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Brian Vaughn committed Feb 19, 2021
1 parent d79faf2 commit 6010e15
Show file tree
Hide file tree
Showing 12 changed files with 357 additions and 30 deletions.
14 changes: 13 additions & 1 deletion packages/react-dom/src/client/ReactDOMRoot.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type RootOptions = {
mutableSources?: Array<MutableSource<any>>,
...
},
unstable_strictModeLevel?: number,
...
};

Expand Down Expand Up @@ -128,7 +129,18 @@ function createRootImpl(
options.hydrationOptions != null &&
options.hydrationOptions.mutableSources) ||
null;
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
const strictModeLevelOverride =
options != null && options.unstable_strictModeLevel != null
? options.unstable_strictModeLevel
: null;

const root = createContainer(
container,
tag,
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
);
markContainerAsRoot(root.current, container);

const rootContainerElement =
Expand Down
5 changes: 4 additions & 1 deletion packages/react-noop-renderer/src/createReactNoop.js
Original file line number Diff line number Diff line change
Expand Up @@ -722,7 +722,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
if (!root) {
const container = {rootID: rootID, pendingChildren: [], children: []};
rootContainers.set(rootID, container);
root = NoopRenderer.createContainer(container, tag, false, null);
root = NoopRenderer.createContainer(container, tag, false, null, null);
roots.set(rootID, root);
}
return root.current.stateNode.containerInfo;
Expand All @@ -740,6 +740,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
ConcurrentRoot,
false,
null,
null,
);
return {
_Scheduler: Scheduler,
Expand All @@ -766,6 +767,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
BlockingRoot,
false,
null,
null,
);
return {
_Scheduler: Scheduler,
Expand All @@ -792,6 +794,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
LegacyRoot,
false,
null,
null,
);
return {
_Scheduler: Scheduler,
Expand Down
41 changes: 34 additions & 7 deletions packages/react-reconciler/src/ReactFiber.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,14 +421,29 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
return workInProgress;
}

export function createHostRootFiber(tag: RootTag): Fiber {
export function createHostRootFiber(
tag: RootTag,
strictModeLevelOverride: null | number,
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
if (enableStrictEffects && createRootStrictEffectsByDefault) {
mode =
ConcurrentMode | BlockingMode | StrictLegacyMode | StrictEffectsMode;
mode = ConcurrentMode | BlockingMode;

if (strictModeLevelOverride !== null) {
if (strictModeLevelOverride >= 1) {
mode |= StrictLegacyMode;
}
if (enableStrictEffects) {
if (strictModeLevelOverride >= 2) {
mode |= StrictEffectsMode;
}
}
} else {
mode = ConcurrentMode | BlockingMode | StrictLegacyMode;
if (enableStrictEffects && createRootStrictEffectsByDefault) {
mode |= StrictLegacyMode | StrictEffectsMode;
} else {
mode |= StrictLegacyMode;
}
}
} else if (tag === BlockingRoot) {
if (enableStrictEffects && createRootStrictEffectsByDefault) {
Expand Down Expand Up @@ -484,8 +499,20 @@ export function createFiberFromTypeAndProps(
break;
case REACT_STRICT_MODE_TYPE:
fiberTag = Mode;
// TODO (StrictEffectsMode) Add support for new strict mode "level" attribute
mode |= StrictLegacyMode;

// Legacy strict mode (<StrictMode> without any level prop) defaults to level 1.
const level = pendingProps.level == null ? 1 : pendingProps.level;

// Levels cascade; higher levels inherit all lower level modes.
// It is explicitly not supported to lower a mode with nesting, only to increase it.
if (level >= 1) {
mode |= StrictLegacyMode;
}
if (enableStrictEffects) {
if (level >= 2) {
mode |= StrictEffectsMode;
}
}
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, lanes, key);
Expand Down
41 changes: 34 additions & 7 deletions packages/react-reconciler/src/ReactFiber.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,14 +421,29 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) {
return workInProgress;
}

export function createHostRootFiber(tag: RootTag): Fiber {
export function createHostRootFiber(
tag: RootTag,
strictModeLevelOverride: null | number,
): Fiber {
let mode;
if (tag === ConcurrentRoot) {
if (enableStrictEffects && createRootStrictEffectsByDefault) {
mode =
ConcurrentMode | BlockingMode | StrictLegacyMode | StrictEffectsMode;
mode = ConcurrentMode | BlockingMode;

if (strictModeLevelOverride !== null) {
if (strictModeLevelOverride >= 1) {
mode |= StrictLegacyMode;
}
if (enableStrictEffects) {
if (strictModeLevelOverride >= 2) {
mode |= StrictEffectsMode;
}
}
} else {
mode = ConcurrentMode | BlockingMode | StrictLegacyMode;
if (enableStrictEffects && createRootStrictEffectsByDefault) {
mode |= StrictLegacyMode | StrictEffectsMode;
} else {
mode |= StrictLegacyMode;
}
}
} else if (tag === BlockingRoot) {
if (enableStrictEffects && createRootStrictEffectsByDefault) {
Expand Down Expand Up @@ -484,8 +499,20 @@ export function createFiberFromTypeAndProps(
break;
case REACT_STRICT_MODE_TYPE:
fiberTag = Mode;
// TODO (StrictEffectsMode) Add support for new strict mode "level" attribute
mode |= StrictLegacyMode;

// Legacy strict mode (<StrictMode> without any level prop) defaults to level 1.
const level = pendingProps.level == null ? 1 : pendingProps.level;

// Levels cascade; higher levels inherit all lower level modes.
// It is explicitly not supported to lower a mode with nesting, only to increase it.
if (level >= 1) {
mode |= StrictLegacyMode;
}
if (enableStrictEffects) {
if (level >= 2) {
mode |= StrictEffectsMode;
}
}
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, lanes, key);
Expand Down
9 changes: 8 additions & 1 deletion packages/react-reconciler/src/ReactFiberReconciler.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,15 @@ export function createContainer(
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
): OpaqueRoot {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
return createFiberRoot(
containerInfo,
tag,
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
);
}

export function updateContainer(
Expand Down
9 changes: 8 additions & 1 deletion packages/react-reconciler/src/ReactFiberReconciler.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,15 @@ export function createContainer(
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
): OpaqueRoot {
return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
return createFiberRoot(
containerInfo,
tag,
hydrate,
hydrationCallbacks,
strictModeLevelOverride,
);
}

export function updateContainer(
Expand Down
3 changes: 2 additions & 1 deletion packages/react-reconciler/src/ReactFiberRoot.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export function createFiberRoot(
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
Expand All @@ -99,7 +100,7 @@ export function createFiberRoot(

// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(tag);
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;

Expand Down
3 changes: 2 additions & 1 deletion packages/react-reconciler/src/ReactFiberRoot.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export function createFiberRoot(
tag: RootTag,
hydrate: boolean,
hydrationCallbacks: null | SuspenseHydrationCallbacks,
strictModeLevelOverride: null | number,
): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
if (enableSuspenseCallback) {
Expand All @@ -99,7 +100,7 @@ export function createFiberRoot(

// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(tag);
const uninitializedFiber = createHostRootFiber(tag, strictModeLevelOverride);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;

Expand Down
8 changes: 3 additions & 5 deletions packages/react-reconciler/src/ReactFiberWorkLoop.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ import {
import {
NoMode,
StrictLegacyMode,
StrictEffectsMode,
ProfileMode,
BlockingMode,
ConcurrentMode,
Expand Down Expand Up @@ -2562,10 +2561,9 @@ function commitDoubleInvokeEffectsInDEV(
hasPassiveEffects: boolean,
) {
if (__DEV__ && enableStrictEffects) {
// Never double-invoke effects outside of StrictEffectsMode.
if ((fiber.mode & StrictEffectsMode) === NoMode) {
return;
}
// TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
// so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
// Maybe not a big deal since this is DEV only behavior.

setCurrentDebugFiberInDEV(fiber);
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
Expand Down
8 changes: 3 additions & 5 deletions packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ import {
import {
NoMode,
StrictLegacyMode,
StrictEffectsMode,
ProfileMode,
BlockingMode,
ConcurrentMode,
Expand Down Expand Up @@ -2562,10 +2561,9 @@ function commitDoubleInvokeEffectsInDEV(
hasPassiveEffects: boolean,
) {
if (__DEV__ && enableStrictEffects) {
// Never double-invoke effects outside of StrictEffectsMode.
if ((fiber.mode & StrictEffectsMode) === NoMode) {
return;
}
// TODO (StrictEffects) Should we set a marker on the root if it contains strict effects
// so we don't traverse unnecessarily? similar to subtreeFlags but just at the root level.
// Maybe not a big deal since this is DEV only behavior.

setCurrentDebugFiberInDEV(fiber);
invokeEffectsInDev(fiber, MountLayoutDev, invokeLayoutEffectUnmountInDEV);
Expand Down
1 change: 1 addition & 0 deletions packages/react-test-renderer/src/ReactTestRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,7 @@ function create(element: React$Element<any>, options: TestRendererOptions) {
isConcurrent ? ConcurrentRoot : LegacyRoot,
false,
null,
null,
);
invariant(root != null, 'something went wrong');
updateContainer(element, root, null, null);
Expand Down
Loading

0 comments on commit 6010e15

Please sign in to comment.