Skip to content

Commit

Permalink
Add StrictMode 'unstable_level' prop and createRoot 'unstable_strictM…
Browse files Browse the repository at this point in the history
…odeLevel' 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
  • Loading branch information
Brian Vaughn committed Feb 22, 2021
1 parent 2431a51 commit 719b02f
Show file tree
Hide file tree
Showing 14 changed files with 408 additions and 38 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
2 changes: 1 addition & 1 deletion packages/react-native-renderer/src/ReactFabric.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ function render(
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(containerTag, LegacyRoot, false, null);
root = createContainer(containerTag, LegacyRoot, false, null, null);
roots.set(containerTag, root);
}
updateContainer(element, root, null, callback);
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-renderer/src/ReactNativeRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ function render(
if (!root) {
// TODO (bvaughn): If we decide to keep the wrapper component,
// We could create a wrapper for containerTag as well to reduce special casing.
root = createContainer(containerTag, LegacyRoot, false, null);
root = createContainer(containerTag, LegacyRoot, false, null, null);
roots.set(containerTag, root);
}
updateContainer(element, root, null, callback);
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
59 changes: 49 additions & 10 deletions packages/react-reconciler/src/ReactFiber.new.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,20 +421,46 @@ 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) {
mode = BlockingMode | StrictLegacyMode | StrictEffectsMode;
mode = BlockingMode;
if (strictModeLevelOverride !== null) {
if (strictModeLevelOverride >= 1) {
mode |= StrictLegacyMode;
}
if (enableStrictEffects) {
if (strictModeLevelOverride >= 2) {
mode |= StrictEffectsMode;
}
}
} else {
mode = BlockingMode | StrictLegacyMode;
if (enableStrictEffects && createRootStrictEffectsByDefault) {
mode |= StrictLegacyMode | StrictEffectsMode;
} else {
mode |= StrictLegacyMode;
}
}
} else {
mode = NoMode;
Expand Down Expand Up @@ -484,8 +510,21 @@ 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.unstable_level == null ? 1 : pendingProps.unstable_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
59 changes: 49 additions & 10 deletions packages/react-reconciler/src/ReactFiber.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,20 +421,46 @@ 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) {
mode = BlockingMode | StrictLegacyMode | StrictEffectsMode;
mode = BlockingMode;
if (strictModeLevelOverride !== null) {
if (strictModeLevelOverride >= 1) {
mode |= StrictLegacyMode;
}
if (enableStrictEffects) {
if (strictModeLevelOverride >= 2) {
mode |= StrictEffectsMode;
}
}
} else {
mode = BlockingMode | StrictLegacyMode;
if (enableStrictEffects && createRootStrictEffectsByDefault) {
mode |= StrictLegacyMode | StrictEffectsMode;
} else {
mode |= StrictLegacyMode;
}
}
} else {
mode = NoMode;
Expand Down Expand Up @@ -484,8 +510,21 @@ 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.unstable_level == null ? 1 : pendingProps.unstable_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 719b02f

Please sign in to comment.