diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 66a31328540e5..62a72dc229618 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -27,6 +27,7 @@ export type RootOptions = { mutableSources?: Array>, ... }, + unstable_strictModeLevel?: number, ... }; @@ -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 = diff --git a/packages/react-native-renderer/src/ReactFabric.js b/packages/react-native-renderer/src/ReactFabric.js index 87fe4e8e2bf1f..03ad183c37df4 100644 --- a/packages/react-native-renderer/src/ReactFabric.js +++ b/packages/react-native-renderer/src/ReactFabric.js @@ -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); diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js index fdf9ebf5ac597..673482d0175cd 100644 --- a/packages/react-native-renderer/src/ReactNativeRenderer.js +++ b/packages/react-native-renderer/src/ReactNativeRenderer.js @@ -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); diff --git a/packages/react-noop-renderer/src/createReactNoop.js b/packages/react-noop-renderer/src/createReactNoop.js index 26c58ffd8445c..743470966b3f0 100644 --- a/packages/react-noop-renderer/src/createReactNoop.js +++ b/packages/react-noop-renderer/src/createReactNoop.js @@ -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; @@ -740,6 +740,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { ConcurrentRoot, false, null, + null, ); return { _Scheduler: Scheduler, @@ -766,6 +767,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { BlockingRoot, false, null, + null, ); return { _Scheduler: Scheduler, @@ -792,6 +794,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) { LegacyRoot, false, null, + null, ); return { _Scheduler: Scheduler, diff --git a/packages/react-reconciler/src/ReactChildFiber.new.js b/packages/react-reconciler/src/ReactChildFiber.new.js index 3435694b7ab0a..222a8730c4fa2 100644 --- a/packages/react-reconciler/src/ReactChildFiber.new.js +++ b/packages/react-reconciler/src/ReactChildFiber.new.js @@ -46,7 +46,7 @@ import { } from './ReactFiber.new'; import {emptyRefsObject} from './ReactFiberClassComponent.new'; import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.new'; -import {StrictMode} from './ReactTypeOfMode'; +import {StrictLegacyMode} from './ReactTypeOfMode'; let didWarnAboutMaps; let didWarnAboutGenerators; @@ -114,7 +114,7 @@ function coerceRef( // TODO: Clean this up once we turn on the string ref warning for // everyone, because the strict mode case will no longer be relevant if ( - (returnFiber.mode & StrictMode || warnAboutStringRefs) && + (returnFiber.mode & StrictLegacyMode || warnAboutStringRefs) && // We warn in ReactElement.js if owner and self are equal for string refs // because these cannot be automatically converted to an arrow function // using a codemod. Therefore, we don't have to warn about string refs again. diff --git a/packages/react-reconciler/src/ReactChildFiber.old.js b/packages/react-reconciler/src/ReactChildFiber.old.js index 7a4ab0b172085..036fa65503c04 100644 --- a/packages/react-reconciler/src/ReactChildFiber.old.js +++ b/packages/react-reconciler/src/ReactChildFiber.old.js @@ -46,7 +46,7 @@ import { } from './ReactFiber.old'; import {emptyRefsObject} from './ReactFiberClassComponent.old'; import {isCompatibleFamilyForHotReloading} from './ReactFiberHotReloading.old'; -import {StrictMode} from './ReactTypeOfMode'; +import {StrictLegacyMode} from './ReactTypeOfMode'; let didWarnAboutMaps; let didWarnAboutGenerators; @@ -114,7 +114,7 @@ function coerceRef( // TODO: Clean this up once we turn on the string ref warning for // everyone, because the strict mode case will no longer be relevant if ( - (returnFiber.mode & StrictMode || warnAboutStringRefs) && + (returnFiber.mode & StrictLegacyMode || warnAboutStringRefs) && // We warn in ReactElement.js if owner and self are equal for string refs // because these cannot be automatically converted to an arrow function // using a codemod. Therefore, we don't have to warn about string refs again. diff --git a/packages/react-reconciler/src/ReactFiber.new.js b/packages/react-reconciler/src/ReactFiber.new.js index 983855ced5685..f1a0f56b23409 100644 --- a/packages/react-reconciler/src/ReactFiber.new.js +++ b/packages/react-reconciler/src/ReactFiber.new.js @@ -19,9 +19,11 @@ import type {OffscreenProps} from './ReactFiberOffscreenComponent'; import invariant from 'shared/invariant'; import { + createRootStrictEffectsByDefault, + enableCache, + enableStrictEffects, enableProfilerTimer, enableScopeAPI, - enableCache, } from 'shared/ReactFeatureFlags'; import {NoFlags, Placement, StaticMask} from './ReactFiberFlags'; import {ConcurrentRoot, BlockingRoot} from './ReactRootTags'; @@ -64,7 +66,8 @@ import { ConcurrentMode, DebugTracingMode, ProfileMode, - StrictMode, + StrictLegacyMode, + StrictEffectsMode, BlockingMode, } from './ReactTypeOfMode'; import { @@ -418,12 +421,47 @@ 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) { - mode = ConcurrentMode | BlockingMode | StrictMode; + mode = ConcurrentMode | BlockingMode; + if (strictModeLevelOverride !== null) { + if (strictModeLevelOverride >= 1) { + mode |= StrictLegacyMode; + } + if (enableStrictEffects) { + if (strictModeLevelOverride >= 2) { + mode |= StrictEffectsMode; + } + } + } else { + if (enableStrictEffects && createRootStrictEffectsByDefault) { + mode |= StrictLegacyMode | StrictEffectsMode; + } else { + mode |= StrictLegacyMode; + } + } } else if (tag === BlockingRoot) { - mode = BlockingMode | StrictMode; + mode = BlockingMode; + if (strictModeLevelOverride !== null) { + if (strictModeLevelOverride >= 1) { + mode |= StrictLegacyMode; + } + if (enableStrictEffects) { + if (strictModeLevelOverride >= 2) { + mode |= StrictEffectsMode; + } + } + } else { + if (enableStrictEffects && createRootStrictEffectsByDefault) { + mode |= StrictLegacyMode | StrictEffectsMode; + } else { + mode |= StrictLegacyMode; + } + } } else { mode = NoMode; } @@ -472,7 +510,21 @@ export function createFiberFromTypeAndProps( break; case REACT_STRICT_MODE_TYPE: fiberTag = Mode; - mode |= StrictMode; + + // Legacy strict mode ( 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); diff --git a/packages/react-reconciler/src/ReactFiber.old.js b/packages/react-reconciler/src/ReactFiber.old.js index ed2ae45f4ec17..6419fd6b261b3 100644 --- a/packages/react-reconciler/src/ReactFiber.old.js +++ b/packages/react-reconciler/src/ReactFiber.old.js @@ -19,9 +19,11 @@ import type {OffscreenProps} from './ReactFiberOffscreenComponent'; import invariant from 'shared/invariant'; import { + createRootStrictEffectsByDefault, + enableCache, + enableStrictEffects, enableProfilerTimer, enableScopeAPI, - enableCache, } from 'shared/ReactFeatureFlags'; import {NoFlags, Placement, StaticMask} from './ReactFiberFlags'; import {ConcurrentRoot, BlockingRoot} from './ReactRootTags'; @@ -64,7 +66,8 @@ import { ConcurrentMode, DebugTracingMode, ProfileMode, - StrictMode, + StrictLegacyMode, + StrictEffectsMode, BlockingMode, } from './ReactTypeOfMode'; import { @@ -418,12 +421,47 @@ 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) { - mode = ConcurrentMode | BlockingMode | StrictMode; + mode = ConcurrentMode | BlockingMode; + if (strictModeLevelOverride !== null) { + if (strictModeLevelOverride >= 1) { + mode |= StrictLegacyMode; + } + if (enableStrictEffects) { + if (strictModeLevelOverride >= 2) { + mode |= StrictEffectsMode; + } + } + } else { + if (enableStrictEffects && createRootStrictEffectsByDefault) { + mode |= StrictLegacyMode | StrictEffectsMode; + } else { + mode |= StrictLegacyMode; + } + } } else if (tag === BlockingRoot) { - mode = BlockingMode | StrictMode; + mode = BlockingMode; + if (strictModeLevelOverride !== null) { + if (strictModeLevelOverride >= 1) { + mode |= StrictLegacyMode; + } + if (enableStrictEffects) { + if (strictModeLevelOverride >= 2) { + mode |= StrictEffectsMode; + } + } + } else { + if (enableStrictEffects && createRootStrictEffectsByDefault) { + mode |= StrictLegacyMode | StrictEffectsMode; + } else { + mode |= StrictLegacyMode; + } + } } else { mode = NoMode; } @@ -472,7 +510,21 @@ export function createFiberFromTypeAndProps( break; case REACT_STRICT_MODE_TYPE: fiberTag = Mode; - mode |= StrictMode; + + // Legacy strict mode ( 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); diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js index 6537d103e2324..6dffcc328927a 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js @@ -125,7 +125,7 @@ import { ConcurrentMode, NoMode, ProfileMode, - StrictMode, + StrictLegacyMode, BlockingMode, } from './ReactTypeOfMode'; import { @@ -357,7 +357,7 @@ function updateForwardRef( ); if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -889,7 +889,7 @@ function updateFunctionComponent( ); if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -1068,7 +1068,7 @@ function finishClassComponent( nextChildren = instance.render(); if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -1478,7 +1478,7 @@ function mountIndeterminateComponent( } } - if (workInProgress.mode & StrictMode) { + if (workInProgress.mode & StrictLegacyMode) { ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null); } @@ -1615,7 +1615,7 @@ function mountIndeterminateComponent( if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.old.js b/packages/react-reconciler/src/ReactFiberBeginWork.old.js index 4c0594329d94e..7801afe9fa231 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.old.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.old.js @@ -125,7 +125,7 @@ import { ConcurrentMode, NoMode, ProfileMode, - StrictMode, + StrictLegacyMode, BlockingMode, } from './ReactTypeOfMode'; import { @@ -357,7 +357,7 @@ function updateForwardRef( ); if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -889,7 +889,7 @@ function updateFunctionComponent( ); if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -1068,7 +1068,7 @@ function finishClassComponent( nextChildren = instance.render(); if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -1478,7 +1478,7 @@ function mountIndeterminateComponent( } } - if (workInProgress.mode & StrictMode) { + if (workInProgress.mode & StrictLegacyMode) { ReactStrictModeWarnings.recordLegacyContextWarning(workInProgress, null); } @@ -1615,7 +1615,7 @@ function mountIndeterminateComponent( if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.new.js b/packages/react-reconciler/src/ReactFiberClassComponent.new.js index 389b4877852db..8259e59a70bdd 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.new.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.new.js @@ -19,7 +19,7 @@ import { enableDebugTracing, enableSchedulingProfiler, warnAboutDeprecatedLifecycles, - enableDoubleInvokingEffects, + enableStrictEffects, } from 'shared/ReactFeatureFlags'; import ReactStrictModeWarnings from './ReactStrictModeWarnings.new'; import {isMounted} from './ReactFiberTreeReflection'; @@ -31,11 +31,10 @@ import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols'; import {resolveDefaultProps} from './ReactFiberLazyComponent.new'; import { - BlockingMode, - ConcurrentMode, DebugTracingMode, NoMode, - StrictMode, + StrictLegacyMode, + StrictEffectsMode, } from './ReactTypeOfMode'; import { @@ -165,7 +164,7 @@ export function applyDerivedStateFromProps( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -318,7 +317,7 @@ function checkShouldComponentUpdate( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -655,7 +654,7 @@ function constructClassInstance( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -862,7 +861,7 @@ function mountClassInstance( } } - if (workInProgress.mode & StrictMode) { + if (workInProgress.mode & StrictLegacyMode) { ReactStrictModeWarnings.recordLegacyContextWarning( workInProgress, instance, @@ -909,8 +908,8 @@ function mountClassInstance( if (typeof instance.componentDidMount === 'function') { if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { // Never double-invoke effects for legacy roots. workInProgress.flags |= MountLayoutDev | Update; @@ -988,8 +987,8 @@ function resumeMountClassInstance( if (typeof instance.componentDidMount === 'function') { if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { // Never double-invoke effects for legacy roots. workInProgress.flags |= MountLayoutDev | Update; @@ -1040,8 +1039,8 @@ function resumeMountClassInstance( if (typeof instance.componentDidMount === 'function') { if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { // Never double-invoke effects for legacy roots. workInProgress.flags |= MountLayoutDev | Update; @@ -1055,8 +1054,8 @@ function resumeMountClassInstance( if (typeof instance.componentDidMount === 'function') { if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { // Never double-invoke effects for legacy roots. workInProgress.flags |= MountLayoutDev | Update; diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.old.js b/packages/react-reconciler/src/ReactFiberClassComponent.old.js index 5f366e7c253a2..fe20322bb15a6 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.old.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.old.js @@ -19,7 +19,7 @@ import { enableDebugTracing, enableSchedulingProfiler, warnAboutDeprecatedLifecycles, - enableDoubleInvokingEffects, + enableStrictEffects, } from 'shared/ReactFeatureFlags'; import ReactStrictModeWarnings from './ReactStrictModeWarnings.old'; import {isMounted} from './ReactFiberTreeReflection'; @@ -31,11 +31,10 @@ import {REACT_CONTEXT_TYPE, REACT_PROVIDER_TYPE} from 'shared/ReactSymbols'; import {resolveDefaultProps} from './ReactFiberLazyComponent.old'; import { - BlockingMode, - ConcurrentMode, DebugTracingMode, NoMode, - StrictMode, + StrictLegacyMode, + StrictEffectsMode, } from './ReactTypeOfMode'; import { @@ -165,7 +164,7 @@ export function applyDerivedStateFromProps( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -318,7 +317,7 @@ function checkShouldComponentUpdate( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -655,7 +654,7 @@ function constructClassInstance( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -862,7 +861,7 @@ function mountClassInstance( } } - if (workInProgress.mode & StrictMode) { + if (workInProgress.mode & StrictLegacyMode) { ReactStrictModeWarnings.recordLegacyContextWarning( workInProgress, instance, @@ -909,8 +908,8 @@ function mountClassInstance( if (typeof instance.componentDidMount === 'function') { if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { // Never double-invoke effects for legacy roots. workInProgress.flags |= MountLayoutDev | Update; @@ -988,8 +987,8 @@ function resumeMountClassInstance( if (typeof instance.componentDidMount === 'function') { if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { // Never double-invoke effects for legacy roots. workInProgress.flags |= MountLayoutDev | Update; @@ -1040,8 +1039,8 @@ function resumeMountClassInstance( if (typeof instance.componentDidMount === 'function') { if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { // Never double-invoke effects for legacy roots. workInProgress.flags |= MountLayoutDev | Update; @@ -1055,8 +1054,8 @@ function resumeMountClassInstance( if (typeof instance.componentDidMount === 'function') { if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { // Never double-invoke effects for legacy roots. workInProgress.flags |= MountLayoutDev | Update; diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index e170114848cc8..33ed1b5c95ce7 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -35,7 +35,7 @@ import { enableSuspenseServerRenderer, enableSuspenseCallback, enableScopeAPI, - enableDoubleInvokingEffects, + enableStrictEffects, } from 'shared/ReactFeatureFlags'; import { FunctionComponent, @@ -2475,9 +2475,9 @@ function ensureCorrectReturnPointer(fiber, expectedReturnFiber) { } function invokeLayoutEffectMountInDEV(fiber: Fiber): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. switch (fiber.tag) { case FunctionComponent: case ForwardRef: @@ -2509,9 +2509,9 @@ function invokeLayoutEffectMountInDEV(fiber: Fiber): void { } function invokePassiveEffectMountInDEV(fiber: Fiber): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. switch (fiber.tag) { case FunctionComponent: case ForwardRef: @@ -2534,9 +2534,9 @@ function invokePassiveEffectMountInDEV(fiber: Fiber): void { } function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. switch (fiber.tag) { case FunctionComponent: case ForwardRef: @@ -2578,9 +2578,9 @@ function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void { } function invokePassiveEffectUnmountInDEV(fiber: Fiber): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. switch (fiber.tag) { case FunctionComponent: case ForwardRef: diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index 7a27bbc48f875..7574eb4fd3d3c 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -35,7 +35,7 @@ import { enableSuspenseServerRenderer, enableSuspenseCallback, enableScopeAPI, - enableDoubleInvokingEffects, + enableStrictEffects, } from 'shared/ReactFeatureFlags'; import { FunctionComponent, @@ -2475,9 +2475,9 @@ function ensureCorrectReturnPointer(fiber, expectedReturnFiber) { } function invokeLayoutEffectMountInDEV(fiber: Fiber): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. switch (fiber.tag) { case FunctionComponent: case ForwardRef: @@ -2509,9 +2509,9 @@ function invokeLayoutEffectMountInDEV(fiber: Fiber): void { } function invokePassiveEffectMountInDEV(fiber: Fiber): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. switch (fiber.tag) { case FunctionComponent: case ForwardRef: @@ -2534,9 +2534,9 @@ function invokePassiveEffectMountInDEV(fiber: Fiber): void { } function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. switch (fiber.tag) { case FunctionComponent: case ForwardRef: @@ -2578,9 +2578,9 @@ function invokeLayoutEffectUnmountInDEV(fiber: Fiber): void { } function invokePassiveEffectUnmountInDEV(fiber: Fiber): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. switch (fiber.tag) { case FunctionComponent: case ForwardRef: diff --git a/packages/react-reconciler/src/ReactFiberHooks.new.js b/packages/react-reconciler/src/ReactFiberHooks.new.js index 32144cc1b0505..9d1fa5f82ed8e 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.new.js +++ b/packages/react-reconciler/src/ReactFiberHooks.new.js @@ -29,14 +29,14 @@ import { enableCache, decoupleUpdatePriorityFromScheduler, enableUseRefAccessWarning, - enableDoubleInvokingEffects, + enableStrictEffects, } from 'shared/ReactFeatureFlags'; import { NoMode, BlockingMode, - ConcurrentMode, DebugTracingMode, + StrictEffectsMode, } from './ReactTypeOfMode'; import { NoLane, @@ -509,8 +509,8 @@ export function bailoutHooks( // complete phase (bubbleProperties). if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { workInProgress.flags &= ~( MountPassiveDevEffect | @@ -1423,8 +1423,8 @@ function mountEffect( } if ( __DEV__ && - enableDoubleInvokingEffects && - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode ) { return mountEffectImpl( MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect, @@ -1461,8 +1461,8 @@ function mountLayoutEffect( ): void { if ( __DEV__ && - enableDoubleInvokingEffects && - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode ) { return mountEffectImpl( MountLayoutDevEffect | UpdateEffect, @@ -1533,8 +1533,8 @@ function mountImperativeHandle( if ( __DEV__ && - enableDoubleInvokingEffects && - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode ) { return mountEffectImpl( MountLayoutDevEffect | UpdateEffect, @@ -1830,7 +1830,11 @@ function mountOpaqueIdentifier(): OpaqueIDType | void { const setId = mountState(id)[1]; if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) { - if (__DEV__ && enableDoubleInvokingEffects) { + if ( + __DEV__ && + enableStrictEffects && + (currentlyRenderingFiber.mode & StrictEffectsMode) === NoMode + ) { currentlyRenderingFiber.flags |= MountPassiveDevEffect | PassiveEffect; } else { currentlyRenderingFiber.flags |= PassiveEffect; diff --git a/packages/react-reconciler/src/ReactFiberHooks.old.js b/packages/react-reconciler/src/ReactFiberHooks.old.js index ce0ac36636562..61f1a17452e4b 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.old.js +++ b/packages/react-reconciler/src/ReactFiberHooks.old.js @@ -29,14 +29,14 @@ import { enableCache, decoupleUpdatePriorityFromScheduler, enableUseRefAccessWarning, - enableDoubleInvokingEffects, + enableStrictEffects, } from 'shared/ReactFeatureFlags'; import { NoMode, BlockingMode, - ConcurrentMode, DebugTracingMode, + StrictEffectsMode, } from './ReactTypeOfMode'; import { NoLane, @@ -509,8 +509,8 @@ export function bailoutHooks( // complete phase (bubbleProperties). if ( __DEV__ && - enableDoubleInvokingEffects && - (workInProgress.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (workInProgress.mode & StrictEffectsMode) !== NoMode ) { workInProgress.flags &= ~( MountPassiveDevEffect | @@ -1423,8 +1423,8 @@ function mountEffect( } if ( __DEV__ && - enableDoubleInvokingEffects && - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode ) { return mountEffectImpl( MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect, @@ -1461,8 +1461,8 @@ function mountLayoutEffect( ): void { if ( __DEV__ && - enableDoubleInvokingEffects && - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode ) { return mountEffectImpl( MountLayoutDevEffect | UpdateEffect, @@ -1533,8 +1533,8 @@ function mountImperativeHandle( if ( __DEV__ && - enableDoubleInvokingEffects && - (currentlyRenderingFiber.mode & (BlockingMode | ConcurrentMode)) !== NoMode + enableStrictEffects && + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode ) { return mountEffectImpl( MountLayoutDevEffect | UpdateEffect, @@ -1830,7 +1830,11 @@ function mountOpaqueIdentifier(): OpaqueIDType | void { const setId = mountState(id)[1]; if ((currentlyRenderingFiber.mode & BlockingMode) === NoMode) { - if (__DEV__ && enableDoubleInvokingEffects) { + if ( + __DEV__ && + enableStrictEffects && + (currentlyRenderingFiber.mode & StrictEffectsMode) === NoMode + ) { currentlyRenderingFiber.flags |= MountPassiveDevEffect | PassiveEffect; } else { currentlyRenderingFiber.flags |= PassiveEffect; diff --git a/packages/react-reconciler/src/ReactFiberReconciler.new.js b/packages/react-reconciler/src/ReactFiberReconciler.new.js index be5ee15970dc7..cc639fa0865f6 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.new.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.new.js @@ -75,7 +75,7 @@ import { resetCurrentFiber as resetCurrentDebugFiberInDEV, setCurrentFiber as setCurrentDebugFiberInDEV, } from './ReactCurrentFiber'; -import {StrictMode} from './ReactTypeOfMode'; +import {StrictLegacyMode} from './ReactTypeOfMode'; import { SyncLane, InputDiscreteHydrationLane, @@ -204,7 +204,7 @@ function findHostInstanceWithWarning( if (hostFiber === null) { return null; } - if (hostFiber.mode & StrictMode) { + if (hostFiber.mode & StrictLegacyMode) { const componentName = getComponentName(fiber.type) || 'Component'; if (!didWarnAboutFindNodeInStrictMode[componentName]) { didWarnAboutFindNodeInStrictMode[componentName] = true; @@ -212,7 +212,7 @@ function findHostInstanceWithWarning( const previousFiber = ReactCurrentFiberCurrent; try { setCurrentDebugFiberInDEV(hostFiber); - if (fiber.mode & StrictMode) { + if (fiber.mode & StrictLegacyMode) { console.error( '%s is deprecated in StrictMode. ' + '%s was passed an instance of %s which is inside StrictMode. ' + @@ -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( diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js index 77032eb24b108..3044bf934daf8 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.old.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.old.js @@ -75,7 +75,7 @@ import { resetCurrentFiber as resetCurrentDebugFiberInDEV, setCurrentFiber as setCurrentDebugFiberInDEV, } from './ReactCurrentFiber'; -import {StrictMode} from './ReactTypeOfMode'; +import {StrictLegacyMode} from './ReactTypeOfMode'; import { SyncLane, InputDiscreteHydrationLane, @@ -204,7 +204,7 @@ function findHostInstanceWithWarning( if (hostFiber === null) { return null; } - if (hostFiber.mode & StrictMode) { + if (hostFiber.mode & StrictLegacyMode) { const componentName = getComponentName(fiber.type) || 'Component'; if (!didWarnAboutFindNodeInStrictMode[componentName]) { didWarnAboutFindNodeInStrictMode[componentName] = true; @@ -212,7 +212,7 @@ function findHostInstanceWithWarning( const previousFiber = ReactCurrentFiberCurrent; try { setCurrentDebugFiberInDEV(hostFiber); - if (fiber.mode & StrictMode) { + if (fiber.mode & StrictLegacyMode) { console.error( '%s is deprecated in StrictMode. ' + '%s was passed an instance of %s which is inside StrictMode. ' + @@ -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( diff --git a/packages/react-reconciler/src/ReactFiberRoot.new.js b/packages/react-reconciler/src/ReactFiberRoot.new.js index 9057137ec61a8..ae6a5bdb596f6 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.new.js +++ b/packages/react-reconciler/src/ReactFiberRoot.new.js @@ -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) { @@ -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; diff --git a/packages/react-reconciler/src/ReactFiberRoot.old.js b/packages/react-reconciler/src/ReactFiberRoot.old.js index 92ec811dd5589..0c0d45c098720 100644 --- a/packages/react-reconciler/src/ReactFiberRoot.old.js +++ b/packages/react-reconciler/src/ReactFiberRoot.old.js @@ -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) { @@ -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; diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 918261a2e3562..7caee07872517 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -32,7 +32,7 @@ import { enableDebugTracing, enableSchedulingProfiler, disableSchedulerTimeoutInWorkLoop, - enableDoubleInvokingEffects, + enableStrictEffects, skipUnmountedBoundaries, enableNativeEventPriorityInference, } from 'shared/ReactFeatureFlags'; @@ -104,7 +104,7 @@ import { } from './ReactFiber.new'; import { NoMode, - StrictMode, + StrictLegacyMode, ProfileMode, BlockingMode, ConcurrentMode, @@ -2070,7 +2070,7 @@ function commitRootImpl(root, renderPriorityLevel) { legacyErrorBoundariesThatAlreadyFailed = null; } - if (__DEV__ && enableDoubleInvokingEffects) { + if (__DEV__ && enableStrictEffects) { if (!rootDidHavePassiveEffects) { commitDoubleInvokeEffectsInDEV(root.current, false); } @@ -2257,7 +2257,7 @@ function flushPassiveEffectsImpl() { markPassiveEffectsStopped(); } - if (__DEV__ && enableDoubleInvokingEffects) { + if (__DEV__ && enableStrictEffects) { commitDoubleInvokeEffectsInDEV(root.current, true); } @@ -2560,11 +2560,10 @@ function commitDoubleInvokeEffectsInDEV( fiber: Fiber, hasPassiveEffects: boolean, ) { - if (__DEV__ && enableDoubleInvokingEffects) { - // Never double-invoke effects for legacy roots. - if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) { - return; - } + if (__DEV__ && enableStrictEffects) { + // 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); @@ -2589,9 +2588,9 @@ function invokeEffectsInDev( fiberFlags: Flags, invokeEffectFn: (fiber: Fiber) => void, ): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. let current = firstChild; let subtreeRoot = null; @@ -2934,7 +2933,7 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void { if (__DEV__) { if ( warnsIfNotActing === true && - (fiber.mode & StrictMode) !== NoMode && + (fiber.mode & StrictLegacyMode) !== NoMode && IsSomeRendererActing.current === false && IsThisRendererActing.current === false ) { diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 12ef384a47527..1467929ce4597 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -32,7 +32,7 @@ import { enableDebugTracing, enableSchedulingProfiler, disableSchedulerTimeoutInWorkLoop, - enableDoubleInvokingEffects, + enableStrictEffects, skipUnmountedBoundaries, enableNativeEventPriorityInference, } from 'shared/ReactFeatureFlags'; @@ -104,7 +104,7 @@ import { } from './ReactFiber.old'; import { NoMode, - StrictMode, + StrictLegacyMode, ProfileMode, BlockingMode, ConcurrentMode, @@ -2070,7 +2070,7 @@ function commitRootImpl(root, renderPriorityLevel) { legacyErrorBoundariesThatAlreadyFailed = null; } - if (__DEV__ && enableDoubleInvokingEffects) { + if (__DEV__ && enableStrictEffects) { if (!rootDidHavePassiveEffects) { commitDoubleInvokeEffectsInDEV(root.current, false); } @@ -2257,7 +2257,7 @@ function flushPassiveEffectsImpl() { markPassiveEffectsStopped(); } - if (__DEV__ && enableDoubleInvokingEffects) { + if (__DEV__ && enableStrictEffects) { commitDoubleInvokeEffectsInDEV(root.current, true); } @@ -2560,11 +2560,10 @@ function commitDoubleInvokeEffectsInDEV( fiber: Fiber, hasPassiveEffects: boolean, ) { - if (__DEV__ && enableDoubleInvokingEffects) { - // Never double-invoke effects for legacy roots. - if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) { - return; - } + if (__DEV__ && enableStrictEffects) { + // 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); @@ -2589,9 +2588,9 @@ function invokeEffectsInDev( fiberFlags: Flags, invokeEffectFn: (fiber: Fiber) => void, ): void { - if (__DEV__ && enableDoubleInvokingEffects) { - // We don't need to re-check for legacy roots here. - // This function will not be called within legacy roots. + if (__DEV__ && enableStrictEffects) { + // We don't need to re-check StrictEffectsMode here. + // This function is only called if that check has already passed. let current = firstChild; let subtreeRoot = null; @@ -2934,7 +2933,7 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void { if (__DEV__) { if ( warnsIfNotActing === true && - (fiber.mode & StrictMode) !== NoMode && + (fiber.mode & StrictLegacyMode) !== NoMode && IsSomeRendererActing.current === false && IsThisRendererActing.current === false ) { diff --git a/packages/react-reconciler/src/ReactStrictModeWarnings.new.js b/packages/react-reconciler/src/ReactStrictModeWarnings.new.js index 5dd09a8cc80da..972c106c632cf 100644 --- a/packages/react-reconciler/src/ReactStrictModeWarnings.new.js +++ b/packages/react-reconciler/src/ReactStrictModeWarnings.new.js @@ -14,7 +14,7 @@ import { setCurrentFiber as setCurrentDebugFiberInDEV, } from './ReactCurrentFiber'; import getComponentName from 'shared/getComponentName'; -import {StrictMode} from './ReactTypeOfMode'; +import {StrictLegacyMode} from './ReactTypeOfMode'; type FiberArray = Array; type FiberToFiberComponentsMap = Map; @@ -33,7 +33,7 @@ if (__DEV__) { let node = fiber; while (node !== null) { - if (node.mode & StrictMode) { + if (node.mode & StrictLegacyMode) { maybeStrictRoot = node; } node = node.return; @@ -78,7 +78,7 @@ if (__DEV__) { } if ( - fiber.mode & StrictMode && + fiber.mode & StrictLegacyMode && typeof instance.UNSAFE_componentWillMount === 'function' ) { pendingUNSAFE_ComponentWillMountWarnings.push(fiber); @@ -92,7 +92,7 @@ if (__DEV__) { } if ( - fiber.mode & StrictMode && + fiber.mode & StrictLegacyMode && typeof instance.UNSAFE_componentWillReceiveProps === 'function' ) { pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber); @@ -106,7 +106,7 @@ if (__DEV__) { } if ( - fiber.mode & StrictMode && + fiber.mode & StrictLegacyMode && typeof instance.UNSAFE_componentWillUpdate === 'function' ) { pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber); diff --git a/packages/react-reconciler/src/ReactStrictModeWarnings.old.js b/packages/react-reconciler/src/ReactStrictModeWarnings.old.js index 5dd09a8cc80da..972c106c632cf 100644 --- a/packages/react-reconciler/src/ReactStrictModeWarnings.old.js +++ b/packages/react-reconciler/src/ReactStrictModeWarnings.old.js @@ -14,7 +14,7 @@ import { setCurrentFiber as setCurrentDebugFiberInDEV, } from './ReactCurrentFiber'; import getComponentName from 'shared/getComponentName'; -import {StrictMode} from './ReactTypeOfMode'; +import {StrictLegacyMode} from './ReactTypeOfMode'; type FiberArray = Array; type FiberToFiberComponentsMap = Map; @@ -33,7 +33,7 @@ if (__DEV__) { let node = fiber; while (node !== null) { - if (node.mode & StrictMode) { + if (node.mode & StrictLegacyMode) { maybeStrictRoot = node; } node = node.return; @@ -78,7 +78,7 @@ if (__DEV__) { } if ( - fiber.mode & StrictMode && + fiber.mode & StrictLegacyMode && typeof instance.UNSAFE_componentWillMount === 'function' ) { pendingUNSAFE_ComponentWillMountWarnings.push(fiber); @@ -92,7 +92,7 @@ if (__DEV__) { } if ( - fiber.mode & StrictMode && + fiber.mode & StrictLegacyMode && typeof instance.UNSAFE_componentWillReceiveProps === 'function' ) { pendingUNSAFE_ComponentWillReceivePropsWarnings.push(fiber); @@ -106,7 +106,7 @@ if (__DEV__) { } if ( - fiber.mode & StrictMode && + fiber.mode & StrictLegacyMode && typeof instance.UNSAFE_componentWillUpdate === 'function' ) { pendingUNSAFE_ComponentWillUpdateWarnings.push(fiber); diff --git a/packages/react-reconciler/src/ReactTypeOfMode.js b/packages/react-reconciler/src/ReactTypeOfMode.js index f6092058d2ba0..a6499be7aca11 100644 --- a/packages/react-reconciler/src/ReactTypeOfMode.js +++ b/packages/react-reconciler/src/ReactTypeOfMode.js @@ -9,11 +9,11 @@ export type TypeOfMode = number; -export const NoMode = 0b00000; -export const StrictMode = 0b00001; -// TODO: Remove BlockingMode and ConcurrentMode by reading from the root -// tag instead -export const BlockingMode = 0b00010; -export const ConcurrentMode = 0b00100; -export const ProfileMode = 0b01000; -export const DebugTracingMode = 0b10000; +export const NoMode = /* */ 0b000000; +// TODO: Remove BlockingMode and ConcurrentMode by reading from the root tag instead +export const BlockingMode = /* */ 0b000001; +export const ConcurrentMode = /* */ 0b000010; +export const ProfileMode = /* */ 0b000100; +export const DebugTracingMode = /* */ 0b001000; +export const StrictLegacyMode = /* */ 0b010000; +export const StrictEffectsMode = /* */ 0b100000; diff --git a/packages/react-reconciler/src/ReactUpdateQueue.new.js b/packages/react-reconciler/src/ReactUpdateQueue.new.js index bb4e9e1bc52dc..0bdcbf580764a 100644 --- a/packages/react-reconciler/src/ReactUpdateQueue.new.js +++ b/packages/react-reconciler/src/ReactUpdateQueue.new.js @@ -104,7 +104,7 @@ import {Callback, ShouldCapture, DidCapture} from './ReactFiberFlags'; import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags'; -import {StrictMode} from './ReactTypeOfMode'; +import {StrictLegacyMode} from './ReactTypeOfMode'; import { markSkippedUpdateLanes, isInterleavedUpdate, @@ -392,7 +392,7 @@ function getStateFromUpdate( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -425,7 +425,7 @@ function getStateFromUpdate( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { diff --git a/packages/react-reconciler/src/ReactUpdateQueue.old.js b/packages/react-reconciler/src/ReactUpdateQueue.old.js index 6b578ef9c80d6..209abfb32e743 100644 --- a/packages/react-reconciler/src/ReactUpdateQueue.old.js +++ b/packages/react-reconciler/src/ReactUpdateQueue.old.js @@ -104,7 +104,7 @@ import {Callback, ShouldCapture, DidCapture} from './ReactFiberFlags'; import {debugRenderPhaseSideEffectsForStrictMode} from 'shared/ReactFeatureFlags'; -import {StrictMode} from './ReactTypeOfMode'; +import {StrictLegacyMode} from './ReactTypeOfMode'; import { markSkippedUpdateLanes, isInterleavedUpdate, @@ -392,7 +392,7 @@ function getStateFromUpdate( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { @@ -425,7 +425,7 @@ function getStateFromUpdate( if (__DEV__) { if ( debugRenderPhaseSideEffectsForStrictMode && - workInProgress.mode & StrictMode + workInProgress.mode & StrictLegacyMode ) { disableLogs(); try { diff --git a/packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.internal.js b/packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.internal.js deleted file mode 100644 index b53b7b719c6ba..0000000000000 --- a/packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.internal.js +++ /dev/null @@ -1,747 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - */ - -'use strict'; - -let React; -let ReactFeatureFlags; -let ReactNoop; -let Scheduler; - -function shouldDoubleInvokingEffects() { - // For now, this feature only exists in the old fork (while the new fork is being bisected). - // Eventually we'll land it in both forks. - return __DEV__; -} - -describe('ReactDoubleInvokeEvents', () => { - beforeEach(() => { - jest.resetModules(); - React = require('react'); - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactNoop = require('react-noop-renderer'); - Scheduler = require('scheduler'); - - ReactFeatureFlags.enableDoubleInvokingEffects = shouldDoubleInvokingEffects(); - }); - - it('should not double invoke effects in legacy mode', () => { - function App({text}) { - React.useEffect(() => { - Scheduler.unstable_yieldValue('useEffect mount'); - return () => Scheduler.unstable_yieldValue('useEffect unmount'); - }); - - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('useLayoutEffect mount'); - return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount'); - }); - - return text; - } - - ReactNoop.act(() => { - ReactNoop.renderLegacySyncRoot(); - }); - - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect mount', - 'useEffect mount', - ]); - }); - - it('should not double invoke class lifecycles in legacy mode', () => { - class App extends React.PureComponent { - componentDidMount() { - Scheduler.unstable_yieldValue('componentDidMount'); - } - - componentDidUpdate() { - Scheduler.unstable_yieldValue('componentDidUpdate'); - } - - componentWillUnmount() { - Scheduler.unstable_yieldValue('componentWillUnmount'); - } - - render() { - return this.props.text; - } - } - - ReactNoop.act(() => { - ReactNoop.renderLegacySyncRoot(); - }); - - expect(Scheduler).toHaveYielded(['componentDidMount']); - }); - - it('should flush double-invoked effects within the same frame as layout effects if there are no passive effects', () => { - function ComponentWithEffects({label}) { - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`); - return () => - Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`); - }); - - return label; - } - - ReactNoop.act(() => { - ReactNoop.render( - <> - - , - ); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toFlushUntilNextPaint([ - 'useLayoutEffect mount "one"', - 'useLayoutEffect unmount "one"', - 'useLayoutEffect mount "one"', - ]); - } else { - expect(Scheduler).toFlushUntilNextPaint([ - 'useLayoutEffect mount "one"', - ]); - } - }); - - ReactNoop.act(() => { - ReactNoop.render( - <> - - - , - ); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toFlushUntilNextPaint([ - // Cleanup and re-run "one" (and "two") since there is no dependencies array. - 'useLayoutEffect unmount "one"', - 'useLayoutEffect mount "one"', - 'useLayoutEffect mount "two"', - - // Since "two" is new, it should be double-invoked. - 'useLayoutEffect unmount "two"', - 'useLayoutEffect mount "two"', - ]); - } else { - expect(Scheduler).toFlushUntilNextPaint([ - 'useLayoutEffect unmount "one"', - 'useLayoutEffect mount "one"', - 'useLayoutEffect mount "two"', - ]); - } - }); - }); - - // This test also verifies that double-invoked effects flush synchronously - // within the same frame as passive effects. - it('should double invoke effects only for newly mounted components', () => { - function ComponentWithEffects({label}) { - React.useEffect(() => { - Scheduler.unstable_yieldValue(`useEffect mount "${label}"`); - return () => - Scheduler.unstable_yieldValue(`useEffect unmount "${label}"`); - }); - - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`); - return () => - Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`); - }); - - return label; - } - - ReactNoop.act(() => { - ReactNoop.render( - <> - - , - ); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toFlushAndYieldThrough([ - 'useLayoutEffect mount "one"', - ]); - expect(Scheduler).toFlushAndYield([ - 'useEffect mount "one"', - 'useLayoutEffect unmount "one"', - 'useEffect unmount "one"', - 'useLayoutEffect mount "one"', - 'useEffect mount "one"', - ]); - } else { - expect(Scheduler).toFlushAndYieldThrough([ - 'useLayoutEffect mount "one"', - ]); - expect(Scheduler).toFlushAndYield(['useEffect mount "one"']); - } - }); - - ReactNoop.act(() => { - ReactNoop.render( - <> - - - , - ); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toFlushAndYieldThrough([ - // Cleanup and re-run "one" (and "two") since there is no dependencies array. - 'useLayoutEffect unmount "one"', - 'useLayoutEffect mount "one"', - 'useLayoutEffect mount "two"', - ]); - expect(Scheduler).toFlushAndYield([ - 'useEffect unmount "one"', - 'useEffect mount "one"', - 'useEffect mount "two"', - - // Since "two" is new, it should be double-invoked. - 'useLayoutEffect unmount "two"', - 'useEffect unmount "two"', - 'useLayoutEffect mount "two"', - 'useEffect mount "two"', - ]); - } else { - expect(Scheduler).toFlushAndYieldThrough([ - 'useLayoutEffect unmount "one"', - 'useLayoutEffect mount "one"', - 'useLayoutEffect mount "two"', - ]); - expect(Scheduler).toFlushAndYield([ - 'useEffect unmount "one"', - 'useEffect mount "one"', - 'useEffect mount "two"', - ]); - } - }); - }); - - it('double invoking for effects for modern roots', () => { - function App({text}) { - React.useEffect(() => { - Scheduler.unstable_yieldValue('useEffect mount'); - return () => Scheduler.unstable_yieldValue('useEffect unmount'); - }); - - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('useLayoutEffect mount'); - return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount'); - }); - - return text; - } - ReactNoop.act(() => { - ReactNoop.render(); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect mount', - 'useEffect mount', - 'useLayoutEffect unmount', - 'useEffect unmount', - 'useLayoutEffect mount', - 'useEffect mount', - ]); - } else { - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect mount', - 'useEffect mount', - ]); - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect unmount', - 'useLayoutEffect mount', - 'useEffect unmount', - 'useEffect mount', - ]); - - ReactNoop.act(() => { - ReactNoop.render(null); - }); - - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect unmount', - 'useEffect unmount', - ]); - }); - - it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => { - function App({text}) { - React.useEffect(() => { - Scheduler.unstable_yieldValue('useEffect One mount'); - return () => Scheduler.unstable_yieldValue('useEffect One unmount'); - }); - - React.useEffect(() => { - Scheduler.unstable_yieldValue('useEffect Two mount'); - return () => Scheduler.unstable_yieldValue('useEffect Two unmount'); - }); - - return text; - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'useEffect One mount', - 'useEffect Two mount', - 'useEffect One unmount', - 'useEffect Two unmount', - 'useEffect One mount', - 'useEffect Two mount', - ]); - } else { - expect(Scheduler).toHaveYielded([ - 'useEffect One mount', - 'useEffect Two mount', - ]); - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - expect(Scheduler).toHaveYielded([ - 'useEffect One unmount', - 'useEffect Two unmount', - 'useEffect One mount', - 'useEffect Two mount', - ]); - - ReactNoop.act(() => { - ReactNoop.render(null); - }); - - expect(Scheduler).toHaveYielded([ - 'useEffect One unmount', - 'useEffect Two unmount', - ]); - }); - - it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => { - function App({text}) { - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('useLayoutEffect One mount'); - return () => - Scheduler.unstable_yieldValue('useLayoutEffect One unmount'); - }); - - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('useLayoutEffect Two mount'); - return () => - Scheduler.unstable_yieldValue('useLayoutEffect Two unmount'); - }); - - return text; - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect One mount', - 'useLayoutEffect Two mount', - 'useLayoutEffect One unmount', - 'useLayoutEffect Two unmount', - 'useLayoutEffect One mount', - 'useLayoutEffect Two mount', - ]); - } else { - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect One mount', - 'useLayoutEffect Two mount', - ]); - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect One unmount', - 'useLayoutEffect Two unmount', - 'useLayoutEffect One mount', - 'useLayoutEffect Two mount', - ]); - - ReactNoop.act(() => { - ReactNoop.render(null); - }); - - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect One unmount', - 'useLayoutEffect Two unmount', - ]); - }); - - it('useEffect and useLayoutEffect is called twice when there is no unmount', () => { - function App({text}) { - React.useEffect(() => { - Scheduler.unstable_yieldValue('useEffect mount'); - }); - - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('useLayoutEffect mount'); - }); - - return text; - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect mount', - 'useEffect mount', - 'useLayoutEffect mount', - 'useEffect mount', - ]); - } else { - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect mount', - 'useEffect mount', - ]); - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect mount', - 'useEffect mount', - ]); - - ReactNoop.act(() => { - ReactNoop.render(null); - }); - - expect(Scheduler).toHaveYielded([]); - }); - - it('passes the right context to class component lifecycles', () => { - class App extends React.PureComponent { - test() {} - - componentDidMount() { - this.test(); - Scheduler.unstable_yieldValue('componentDidMount'); - } - - componentDidUpdate() { - this.test(); - Scheduler.unstable_yieldValue('componentDidUpdate'); - } - - componentWillUnmount() { - this.test(); - Scheduler.unstable_yieldValue('componentWillUnmount'); - } - - render() { - return null; - } - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'componentDidMount', - 'componentWillUnmount', - 'componentDidMount', - ]); - } else { - expect(Scheduler).toHaveYielded(['componentDidMount']); - } - }); - - it('double invoking works for class components', () => { - class App extends React.PureComponent { - componentDidMount() { - Scheduler.unstable_yieldValue('componentDidMount'); - } - - componentDidUpdate() { - Scheduler.unstable_yieldValue('componentDidUpdate'); - } - - componentWillUnmount() { - Scheduler.unstable_yieldValue('componentWillUnmount'); - } - - render() { - return this.props.text; - } - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'componentDidMount', - 'componentWillUnmount', - 'componentDidMount', - ]); - } else { - expect(Scheduler).toHaveYielded(['componentDidMount']); - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - expect(Scheduler).toHaveYielded(['componentDidUpdate']); - - ReactNoop.act(() => { - ReactNoop.render(null); - }); - - expect(Scheduler).toHaveYielded(['componentWillUnmount']); - }); - - it('double flushing passive effects only results in one double invoke', () => { - function App({text}) { - const [state, setState] = React.useState(0); - React.useEffect(() => { - if (state !== 1) { - setState(1); - } - Scheduler.unstable_yieldValue('useEffect mount'); - return () => Scheduler.unstable_yieldValue('useEffect unmount'); - }); - - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('useLayoutEffect mount'); - return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount'); - }); - - Scheduler.unstable_yieldValue(text); - return text; - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'mount', - 'useLayoutEffect mount', - 'useEffect mount', - 'useLayoutEffect unmount', - 'useEffect unmount', - 'useLayoutEffect mount', - 'useEffect mount', - 'mount', - 'useLayoutEffect unmount', - 'useLayoutEffect mount', - 'useEffect unmount', - 'useEffect mount', - ]); - } else { - expect(Scheduler).toHaveYielded([ - 'mount', - 'useLayoutEffect mount', - 'useEffect mount', - 'mount', - 'useLayoutEffect unmount', - 'useLayoutEffect mount', - 'useEffect unmount', - 'useEffect mount', - ]); - } - }); - - it('newly mounted components after initial mount get double invoked', () => { - let _setShowChild; - function Child() { - React.useEffect(() => { - Scheduler.unstable_yieldValue('Child useEffect mount'); - return () => Scheduler.unstable_yieldValue('Child useEffect unmount'); - }); - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('Child useLayoutEffect mount'); - return () => - Scheduler.unstable_yieldValue('Child useLayoutEffect unmount'); - }); - - return null; - } - - function App() { - const [showChild, setShowChild] = React.useState(false); - _setShowChild = setShowChild; - React.useEffect(() => { - Scheduler.unstable_yieldValue('App useEffect mount'); - return () => Scheduler.unstable_yieldValue('App useEffect unmount'); - }); - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('App useLayoutEffect mount'); - return () => - Scheduler.unstable_yieldValue('App useLayoutEffect unmount'); - }); - - return showChild && ; - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'App useLayoutEffect mount', - 'App useEffect mount', - 'App useLayoutEffect unmount', - 'App useEffect unmount', - 'App useLayoutEffect mount', - 'App useEffect mount', - ]); - } else { - expect(Scheduler).toHaveYielded([ - 'App useLayoutEffect mount', - 'App useEffect mount', - ]); - } - - ReactNoop.act(() => { - _setShowChild(true); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'App useLayoutEffect unmount', - 'Child useLayoutEffect mount', - 'App useLayoutEffect mount', - 'App useEffect unmount', - 'Child useEffect mount', - 'App useEffect mount', - 'Child useLayoutEffect unmount', - 'Child useEffect unmount', - 'Child useLayoutEffect mount', - 'Child useEffect mount', - ]); - } else { - expect(Scheduler).toHaveYielded([ - 'App useLayoutEffect unmount', - 'Child useLayoutEffect mount', - 'App useLayoutEffect mount', - 'App useEffect unmount', - 'Child useEffect mount', - 'App useEffect mount', - ]); - } - }); - - it('classes and functions are double invoked together correctly', () => { - class ClassChild extends React.PureComponent { - componentDidMount() { - Scheduler.unstable_yieldValue('componentDidMount'); - } - - componentWillUnmount() { - Scheduler.unstable_yieldValue('componentWillUnmount'); - } - - render() { - return this.props.text; - } - } - - function FunctionChild({text}) { - React.useEffect(() => { - Scheduler.unstable_yieldValue('useEffect mount'); - return () => Scheduler.unstable_yieldValue('useEffect unmount'); - }); - React.useLayoutEffect(() => { - Scheduler.unstable_yieldValue('useLayoutEffect mount'); - return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount'); - }); - return text; - } - - function App({text}) { - return ( - <> - - - - ); - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - if (shouldDoubleInvokingEffects()) { - expect(Scheduler).toHaveYielded([ - 'componentDidMount', - 'useLayoutEffect mount', - 'useEffect mount', - 'componentWillUnmount', - 'useLayoutEffect unmount', - 'useEffect unmount', - 'componentDidMount', - 'useLayoutEffect mount', - 'useEffect mount', - ]); - } else { - expect(Scheduler).toHaveYielded([ - 'componentDidMount', - 'useLayoutEffect mount', - 'useEffect mount', - ]); - } - - ReactNoop.act(() => { - ReactNoop.render(); - }); - - expect(Scheduler).toHaveYielded([ - 'useLayoutEffect unmount', - 'useLayoutEffect mount', - 'useEffect unmount', - 'useEffect mount', - ]); - - ReactNoop.act(() => { - ReactNoop.render(null); - }); - - expect(Scheduler).toHaveYielded([ - 'componentWillUnmount', - 'useLayoutEffect unmount', - 'useEffect unmount', - ]); - }); -}); diff --git a/packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.js b/packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js similarity index 99% rename from packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.js rename to packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js index 5b6abbc4f6593..13f7a9ac543c2 100644 --- a/packages/react-reconciler/src/__tests__/ReactDoubleInvokeEvents-test.js +++ b/packages/react-reconciler/src/__tests__/StrictEffectsMode-test.js @@ -14,7 +14,7 @@ let ReactTestRenderer; let Scheduler; let act; -describe('ReactDoubleInvokeEvents', () => { +describe('StrictEffectsMode', () => { beforeEach(() => { jest.resetModules(); React = require('react'); @@ -27,7 +27,8 @@ describe('ReactDoubleInvokeEvents', () => { return gate( flags => flags.build === 'development' && - flags.enableDoubleInvokingEffects && + flags.enableStrictEffects && + flags.createRootStrictEffectsByDefault && flags.dfsEffectsRefactor, ); } diff --git a/packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js b/packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js new file mode 100644 index 0000000000000..469f43348beaf --- /dev/null +++ b/packages/react-reconciler/src/__tests__/StrictEffectsModeDefaults-test.internal.js @@ -0,0 +1,635 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +let React; +let ReactNoop; +let Scheduler; + +describe('StrictEffectsMode defaults', () => { + beforeEach(() => { + jest.resetModules(); + + React = require('react'); + ReactNoop = require('react-noop-renderer'); + Scheduler = require('scheduler'); + + const ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.enableStrictEffects = __DEV__; + ReactFeatureFlags.createRootStrictEffectsByDefault = __DEV__; + }); + + it('should not double invoke effects in legacy mode', () => { + function App({text}) { + React.useEffect(() => { + Scheduler.unstable_yieldValue('useEffect mount'); + return () => Scheduler.unstable_yieldValue('useEffect unmount'); + }); + + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('useLayoutEffect mount'); + return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount'); + }); + + return text; + } + + ReactNoop.act(() => { + ReactNoop.renderLegacySyncRoot(); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect mount', + 'useEffect mount', + ]); + }); + + it('should not double invoke class lifecycles in legacy mode', () => { + class App extends React.PureComponent { + componentDidMount() { + Scheduler.unstable_yieldValue('componentDidMount'); + } + + componentDidUpdate() { + Scheduler.unstable_yieldValue('componentDidUpdate'); + } + + componentWillUnmount() { + Scheduler.unstable_yieldValue('componentWillUnmount'); + } + + render() { + return this.props.text; + } + } + + ReactNoop.act(() => { + ReactNoop.renderLegacySyncRoot(); + }); + + expect(Scheduler).toHaveYielded(['componentDidMount']); + }); + + if (__DEV__) { + it('should flush double-invoked effects within the same frame as layout effects if there are no passive effects', () => { + function ComponentWithEffects({label}) { + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`); + return () => + Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`); + }); + + return label; + } + + ReactNoop.act(() => { + ReactNoop.render( + <> + + , + ); + + expect(Scheduler).toFlushUntilNextPaint([ + 'useLayoutEffect mount "one"', + 'useLayoutEffect unmount "one"', + 'useLayoutEffect mount "one"', + ]); + }); + + ReactNoop.act(() => { + ReactNoop.render( + <> + + + , + ); + + expect(Scheduler).toFlushUntilNextPaint([ + // Cleanup and re-run "one" (and "two") since there is no dependencies array. + 'useLayoutEffect unmount "one"', + 'useLayoutEffect mount "one"', + 'useLayoutEffect mount "two"', + + // Since "two" is new, it should be double-invoked. + 'useLayoutEffect unmount "two"', + 'useLayoutEffect mount "two"', + ]); + }); + }); + + // This test also verifies that double-invoked effects flush synchronously + // within the same frame as passive effects. + it('should double invoke effects only for newly mounted components', () => { + function ComponentWithEffects({label}) { + React.useEffect(() => { + Scheduler.unstable_yieldValue(`useEffect mount "${label}"`); + return () => + Scheduler.unstable_yieldValue(`useEffect unmount "${label}"`); + }); + + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue(`useLayoutEffect mount "${label}"`); + return () => + Scheduler.unstable_yieldValue(`useLayoutEffect unmount "${label}"`); + }); + + return label; + } + + ReactNoop.act(() => { + ReactNoop.render( + <> + + , + ); + + expect(Scheduler).toFlushAndYieldThrough([ + 'useLayoutEffect mount "one"', + ]); + expect(Scheduler).toFlushAndYield([ + 'useEffect mount "one"', + 'useLayoutEffect unmount "one"', + 'useEffect unmount "one"', + 'useLayoutEffect mount "one"', + 'useEffect mount "one"', + ]); + }); + + ReactNoop.act(() => { + ReactNoop.render( + <> + + + , + ); + + expect(Scheduler).toFlushAndYieldThrough([ + // Cleanup and re-run "one" (and "two") since there is no dependencies array. + 'useLayoutEffect unmount "one"', + 'useLayoutEffect mount "one"', + 'useLayoutEffect mount "two"', + ]); + expect(Scheduler).toFlushAndYield([ + 'useEffect unmount "one"', + 'useEffect mount "one"', + 'useEffect mount "two"', + + // Since "two" is new, it should be double-invoked. + 'useLayoutEffect unmount "two"', + 'useEffect unmount "two"', + 'useLayoutEffect mount "two"', + 'useEffect mount "two"', + ]); + }); + }); + + it('double invoking for effects for modern roots', () => { + function App({text}) { + React.useEffect(() => { + Scheduler.unstable_yieldValue('useEffect mount'); + return () => Scheduler.unstable_yieldValue('useEffect unmount'); + }); + + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('useLayoutEffect mount'); + return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount'); + }); + + return text; + } + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect mount', + 'useEffect mount', + 'useLayoutEffect unmount', + 'useEffect unmount', + 'useLayoutEffect mount', + 'useEffect mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect unmount', + 'useLayoutEffect mount', + 'useEffect unmount', + 'useEffect mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(null); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect unmount', + 'useEffect unmount', + ]); + }); + + it('multiple effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => { + function App({text}) { + React.useEffect(() => { + Scheduler.unstable_yieldValue('useEffect One mount'); + return () => Scheduler.unstable_yieldValue('useEffect One unmount'); + }); + + React.useEffect(() => { + Scheduler.unstable_yieldValue('useEffect Two mount'); + return () => Scheduler.unstable_yieldValue('useEffect Two unmount'); + }); + + return text; + } + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'useEffect One mount', + 'useEffect Two mount', + 'useEffect One unmount', + 'useEffect Two unmount', + 'useEffect One mount', + 'useEffect Two mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'useEffect One unmount', + 'useEffect Two unmount', + 'useEffect One mount', + 'useEffect Two mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(null); + }); + + expect(Scheduler).toHaveYielded([ + 'useEffect One unmount', + 'useEffect Two unmount', + ]); + }); + + it('multiple layout effects are double invoked in the right order (all mounted, all unmounted, all remounted)', () => { + function App({text}) { + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('useLayoutEffect One mount'); + return () => + Scheduler.unstable_yieldValue('useLayoutEffect One unmount'); + }); + + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('useLayoutEffect Two mount'); + return () => + Scheduler.unstable_yieldValue('useLayoutEffect Two unmount'); + }); + + return text; + } + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect One mount', + 'useLayoutEffect Two mount', + 'useLayoutEffect One unmount', + 'useLayoutEffect Two unmount', + 'useLayoutEffect One mount', + 'useLayoutEffect Two mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect One unmount', + 'useLayoutEffect Two unmount', + 'useLayoutEffect One mount', + 'useLayoutEffect Two mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(null); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect One unmount', + 'useLayoutEffect Two unmount', + ]); + }); + + it('useEffect and useLayoutEffect is called twice when there is no unmount', () => { + function App({text}) { + React.useEffect(() => { + Scheduler.unstable_yieldValue('useEffect mount'); + }); + + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('useLayoutEffect mount'); + }); + + return text; + } + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect mount', + 'useEffect mount', + 'useLayoutEffect mount', + 'useEffect mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect mount', + 'useEffect mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(null); + }); + + expect(Scheduler).toHaveYielded([]); + }); + + it('passes the right context to class component lifecycles', () => { + class App extends React.PureComponent { + test() {} + + componentDidMount() { + this.test(); + Scheduler.unstable_yieldValue('componentDidMount'); + } + + componentDidUpdate() { + this.test(); + Scheduler.unstable_yieldValue('componentDidUpdate'); + } + + componentWillUnmount() { + this.test(); + Scheduler.unstable_yieldValue('componentWillUnmount'); + } + + render() { + return null; + } + } + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'componentDidMount', + 'componentWillUnmount', + 'componentDidMount', + ]); + }); + + it('double invoking works for class components', () => { + class App extends React.PureComponent { + componentDidMount() { + Scheduler.unstable_yieldValue('componentDidMount'); + } + + componentDidUpdate() { + Scheduler.unstable_yieldValue('componentDidUpdate'); + } + + componentWillUnmount() { + Scheduler.unstable_yieldValue('componentWillUnmount'); + } + + render() { + return this.props.text; + } + } + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'componentDidMount', + 'componentWillUnmount', + 'componentDidMount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded(['componentDidUpdate']); + + ReactNoop.act(() => { + ReactNoop.render(null); + }); + + expect(Scheduler).toHaveYielded(['componentWillUnmount']); + }); + + it('double flushing passive effects only results in one double invoke', () => { + function App({text}) { + const [state, setState] = React.useState(0); + React.useEffect(() => { + if (state !== 1) { + setState(1); + } + Scheduler.unstable_yieldValue('useEffect mount'); + return () => Scheduler.unstable_yieldValue('useEffect unmount'); + }); + + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('useLayoutEffect mount'); + return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount'); + }); + + Scheduler.unstable_yieldValue(text); + return text; + } + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'mount', + 'useLayoutEffect mount', + 'useEffect mount', + 'useLayoutEffect unmount', + 'useEffect unmount', + 'useLayoutEffect mount', + 'useEffect mount', + 'mount', + 'useLayoutEffect unmount', + 'useLayoutEffect mount', + 'useEffect unmount', + 'useEffect mount', + ]); + }); + + it('newly mounted components after initial mount get double invoked', () => { + let _setShowChild; + function Child() { + React.useEffect(() => { + Scheduler.unstable_yieldValue('Child useEffect mount'); + return () => Scheduler.unstable_yieldValue('Child useEffect unmount'); + }); + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('Child useLayoutEffect mount'); + return () => + Scheduler.unstable_yieldValue('Child useLayoutEffect unmount'); + }); + + return null; + } + + function App() { + const [showChild, setShowChild] = React.useState(false); + _setShowChild = setShowChild; + React.useEffect(() => { + Scheduler.unstable_yieldValue('App useEffect mount'); + return () => Scheduler.unstable_yieldValue('App useEffect unmount'); + }); + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('App useLayoutEffect mount'); + return () => + Scheduler.unstable_yieldValue('App useLayoutEffect unmount'); + }); + + return showChild && ; + } + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'App useLayoutEffect mount', + 'App useEffect mount', + 'App useLayoutEffect unmount', + 'App useEffect unmount', + 'App useLayoutEffect mount', + 'App useEffect mount', + ]); + + ReactNoop.act(() => { + _setShowChild(true); + }); + + expect(Scheduler).toHaveYielded([ + 'App useLayoutEffect unmount', + 'Child useLayoutEffect mount', + 'App useLayoutEffect mount', + 'App useEffect unmount', + 'Child useEffect mount', + 'App useEffect mount', + 'Child useLayoutEffect unmount', + 'Child useEffect unmount', + 'Child useLayoutEffect mount', + 'Child useEffect mount', + ]); + }); + + it('classes and functions are double invoked together correctly', () => { + class ClassChild extends React.PureComponent { + componentDidMount() { + Scheduler.unstable_yieldValue('componentDidMount'); + } + + componentWillUnmount() { + Scheduler.unstable_yieldValue('componentWillUnmount'); + } + + render() { + return this.props.text; + } + } + + function FunctionChild({text}) { + React.useEffect(() => { + Scheduler.unstable_yieldValue('useEffect mount'); + return () => Scheduler.unstable_yieldValue('useEffect unmount'); + }); + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('useLayoutEffect mount'); + return () => Scheduler.unstable_yieldValue('useLayoutEffect unmount'); + }); + return text; + } + + function App({text}) { + return ( + <> + + + + ); + } + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'componentDidMount', + 'useLayoutEffect mount', + 'useEffect mount', + 'componentWillUnmount', + 'useLayoutEffect unmount', + 'useEffect unmount', + 'componentDidMount', + 'useLayoutEffect mount', + 'useEffect mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(); + }); + + expect(Scheduler).toHaveYielded([ + 'useLayoutEffect unmount', + 'useLayoutEffect mount', + 'useEffect unmount', + 'useEffect mount', + ]); + + ReactNoop.act(() => { + ReactNoop.render(null); + }); + + expect(Scheduler).toHaveYielded([ + 'componentWillUnmount', + 'useLayoutEffect unmount', + 'useEffect unmount', + ]); + }); + } +}); diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js index c4eea12e8a550..ff80440379f14 100644 --- a/packages/react-test-renderer/src/ReactTestRenderer.js +++ b/packages/react-test-renderer/src/ReactTestRenderer.js @@ -451,6 +451,7 @@ function create(element: React$Element, options: TestRendererOptions) { isConcurrent ? ConcurrentRoot : LegacyRoot, false, null, + null, ); invariant(root != null, 'something went wrong'); updateContainer(element, root, null, null); diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js index a2e27073a3f79..d6e44ab820960 100644 --- a/packages/react/src/__tests__/ReactProfiler-test.internal.js +++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js @@ -4875,7 +4875,8 @@ describe('Profiler', () => { if (__DEV__) { it('double invoking does not disconnect wrapped async work', () => { - ReactFeatureFlags.enableDoubleInvokingEffects = true; + ReactFeatureFlags.enableStrictEffects = true; + ReactFeatureFlags.createRootStrictEffectsByDefault = true; const callback = jest.fn(() => { const wrappedInteractions = SchedulerTracing.unstable_getCurrent(); diff --git a/packages/react/src/__tests__/ReactStrictMode-test.internal.js b/packages/react/src/__tests__/ReactStrictMode-test.internal.js new file mode 100644 index 0000000000000..cb567340d3983 --- /dev/null +++ b/packages/react/src/__tests__/ReactStrictMode-test.internal.js @@ -0,0 +1,264 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails react-core + */ + +'use strict'; + +describe('ReactStrictMode', () => { + let React; + let ReactDOM; + let act; + + beforeEach(() => { + jest.resetModules(); + React = require('react'); + ReactDOM = require('react-dom'); + + const TestUtils = require('react-dom/test-utils'); + act = TestUtils.unstable_concurrentAct; + + const ReactFeatureFlags = require('shared/ReactFeatureFlags'); + ReactFeatureFlags.enableStrictEffects = __DEV__; + }); + + describe('levels', () => { + let log; + + beforeEach(() => { + log = []; + }); + + function Component({label}) { + React.useEffect(() => { + log.push(`${label}: useEffect mount`); + return () => log.push(`${label}: useEffect unmount`); + }); + + React.useLayoutEffect(() => { + log.push(`${label}: useLayoutEffect mount`); + return () => log.push(`${label}: useLayoutEffect unmount`); + }); + + log.push(`${label}: render`); + + return null; + } + + // @gate experimental + it('should support overriding default via createRoot option', () => { + act(() => { + const container = document.createElement('div'); + const root = ReactDOM.createRoot(container, { + unstable_strictModeLevel: 0, + }); + root.render(); + }); + + expect(log).toEqual([ + 'A: render', + 'A: useLayoutEffect mount', + 'A: useEffect mount', + ]); + }); + + // @gate experimental + it('should support overriding default via createBlockingRoot option', () => { + act(() => { + const container = document.createElement('div'); + const root = ReactDOM.createBlockingRoot(container, { + unstable_strictModeLevel: 0, + }); + root.render(); + }); + + expect(log).toEqual([ + 'A: render', + 'A: useLayoutEffect mount', + 'A: useEffect mount', + ]); + }); + + // @gate experimental + it('should disable strict mode if level 0 is specified', () => { + act(() => { + const container = document.createElement('div'); + const root = ReactDOM.createRoot(container, { + unstable_strictModeLevel: 0, + }); + root.render( + + + , + ); + }); + + expect(log).toEqual([ + 'A: render', + 'A: useLayoutEffect mount', + 'A: useEffect mount', + ]); + }); + + if (__DEV__) { + // @gate experimental + it('should default to level 1 (legacy mode)', () => { + act(() => { + const container = document.createElement('div'); + const root = ReactDOM.createRoot(container); + root.render( + + + , + ); + }); + + expect(log).toEqual([ + 'A: render', + 'A: render', + 'A: useLayoutEffect mount', + 'A: useEffect mount', + ]); + }); + + // @gate experimental + it('should support level 1 (legacy mode)', () => { + act(() => { + const container = document.createElement('div'); + const root = ReactDOM.createRoot(container); + root.render( + + + , + ); + }); + + expect(log).toEqual([ + 'A: render', + 'A: render', + 'A: useLayoutEffect mount', + 'A: useEffect mount', + ]); + }); + + // @gate experimental + it('should support level 2 (legacy + strict effects mode)', () => { + act(() => { + const container = document.createElement('div'); + const root = ReactDOM.createRoot(container); + root.render( + + + , + ); + }); + + expect(log).toEqual([ + 'A: render', + 'A: render', + 'A: useLayoutEffect mount', + 'A: useEffect mount', + 'A: useLayoutEffect unmount', + 'A: useEffect unmount', + 'A: useLayoutEffect mount', + 'A: useEffect mount', + ]); + }); + + // @gate experimental + it('should allow level to be increased with nesting', () => { + act(() => { + const container = document.createElement('div'); + const root = ReactDOM.createRoot(container, { + unstable_strictModeLevel: 0, + }); + root.render( + <> + + + + + + + , + + , + , + ); + }); + + expect(log).toEqual([ + 'A: render', + 'B: render', + 'B: render', + 'C: render', + 'C: render', + 'A: useLayoutEffect mount', + 'B: useLayoutEffect mount', + 'C: useLayoutEffect mount', + 'A: useEffect mount', + 'B: useEffect mount', + 'C: useEffect mount', + 'C: useLayoutEffect unmount', + 'C: useEffect unmount', + 'C: useLayoutEffect mount', + 'C: useEffect mount', + ]); + }); + + // @gate experimental + it('should not allow level to be decreased with nesting', () => { + act(() => { + const container = document.createElement('div'); + const root = ReactDOM.createRoot(container, { + unstable_strictModeLevel: 2, + }); + root.render( + <> + + + + + + + , + + , + , + ); + }); + + expect(log).toEqual([ + 'A: render', + 'A: render', + 'B: render', + 'B: render', + 'C: render', + 'C: render', + 'A: useLayoutEffect mount', + 'B: useLayoutEffect mount', + 'C: useLayoutEffect mount', + 'A: useEffect mount', + 'B: useEffect mount', + 'C: useEffect mount', + 'A: useLayoutEffect unmount', + 'B: useLayoutEffect unmount', + 'C: useLayoutEffect unmount', + 'A: useEffect unmount', + 'B: useEffect unmount', + 'C: useEffect unmount', + 'A: useLayoutEffect mount', + 'B: useLayoutEffect mount', + 'C: useLayoutEffect mount', + 'A: useEffect mount', + 'B: useEffect mount', + 'C: useEffect mount', + ]); + }); + } + }); +}); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 778881bc5837d..953eb7cd56615 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -20,9 +20,17 @@ export const enableDebugTracing = false; export const enableSchedulingProfiler = __PROFILE__ && __EXPERIMENTAL__; // Helps identify side effects in render-phase lifecycle hooks and setState -// reducers by double invoking them in Strict Mode. +// reducers by double invoking them in StrictLegacyMode. export const debugRenderPhaseSideEffectsForStrictMode = __DEV__; +// Helps identify code that is not safe for planned Offscreen API and Suspense semantics; +// this feature flag only impacts StrictEffectsMode. +export const enableStrictEffects = false; + +// If TRUE, trees rendered with createRoot (and createBlockingRoot) APIs will be StrictEffectsMode. +// If FALSE, these trees will be StrictLegacyMode. +export const createRootStrictEffectsByDefault = false; + // To preserve the "Pause on caught exceptions" behavior of the debugger, we // replay the begin phase of a failed component inside invokeGuardedCallback. export const replayFailedUnitOfWorkWithInvokeGuardedCallback = __DEV__; @@ -139,8 +147,6 @@ export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; -export const enableDoubleInvokingEffects = false; - export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 6544be4cbe040..2f4406b2ceafa 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -52,7 +52,8 @@ export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; -export const enableDoubleInvokingEffects = false; +export const enableStrictEffects = false; +export const createRootStrictEffectsByDefault = false; export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index a6482da52d524..35e1067646d94 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; -export const enableDoubleInvokingEffects = false; +export const enableStrictEffects = false; +export const createRootStrictEffectsByDefault = false; export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 343bb609b4d25..67ecdb72983c7 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; -export const enableDoubleInvokingEffects = false; +export const enableStrictEffects = false; +export const createRootStrictEffectsByDefault = false; export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index e9ad4240a6fc3..9ed0be59689e7 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; -export const enableDoubleInvokingEffects = false; +export const enableStrictEffects = false; +export const createRootStrictEffectsByDefault = false; export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 2afc959952aca..2e7513a8ddf15 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; -export const enableDoubleInvokingEffects = true; +export const enableStrictEffects = true; +export const createRootStrictEffectsByDefault = false; export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.js b/packages/shared/forks/ReactFeatureFlags.testing.js index d79a63c577892..5a362e8a31419 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.js @@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = false; -export const enableDoubleInvokingEffects = false; +export const enableStrictEffects = false; +export const createRootStrictEffectsByDefault = false; export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; diff --git a/packages/shared/forks/ReactFeatureFlags.testing.www.js b/packages/shared/forks/ReactFeatureFlags.testing.www.js index 732360618f6bf..54942f0c7cd2d 100644 --- a/packages/shared/forks/ReactFeatureFlags.testing.www.js +++ b/packages/shared/forks/ReactFeatureFlags.testing.www.js @@ -51,7 +51,8 @@ export const deferRenderPhaseUpdateToNextBatch = true; export const decoupleUpdatePriorityFromScheduler = false; export const enableDiscreteEventFlushingChange = true; -export const enableDoubleInvokingEffects = false; +export const enableStrictEffects = false; +export const createRootStrictEffectsByDefault = false; export const enableUseRefAccessWarning = false; export const enableRecursiveCommitTraversal = false; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index daf49b2ab2255..1d9f89699f317 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -50,7 +50,8 @@ export const enableTrustedTypesIntegration = false; export const disableSchedulerTimeoutBasedOnReactExpirationTime = false; export const disableNativeComponentFrames = false; -export const enableDoubleInvokingEffects = false; +export const createRootStrictEffectsByDefault = false; +export const enableStrictEffects = false; export const enableUseRefAccessWarning = __VARIANT__; export const enableProfilerNestedUpdateScheduledHook = __VARIANT__; diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 2d983a3e8f81d..5e5cd2105b383 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -27,7 +27,8 @@ export const { decoupleUpdatePriorityFromScheduler, enableDebugTracing, skipUnmountedBoundaries, - enableDoubleInvokingEffects, + enableStrictEffects, + createRootStrictEffectsByDefault, enableUseRefAccessWarning, disableNativeComponentFrames, disableSchedulerTimeoutInWorkLoop,