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..d1a4837fdba01 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 { @@ -421,9 +424,18 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) { export function createHostRootFiber(tag: RootTag): Fiber { let mode; if (tag === ConcurrentRoot) { - mode = ConcurrentMode | BlockingMode | StrictMode; + if (enableStrictEffects && createRootStrictEffectsByDefault) { + mode = + ConcurrentMode | BlockingMode | StrictLegacyMode | StrictEffectsMode; + } else { + mode = ConcurrentMode | BlockingMode | StrictLegacyMode; + } } else if (tag === BlockingRoot) { - mode = BlockingMode | StrictMode; + if (enableStrictEffects && createRootStrictEffectsByDefault) { + mode = BlockingMode | StrictLegacyMode | StrictEffectsMode; + } else { + mode = BlockingMode | StrictLegacyMode; + } } else { mode = NoMode; } @@ -472,7 +484,8 @@ export function createFiberFromTypeAndProps( break; case REACT_STRICT_MODE_TYPE: fiberTag = Mode; - mode |= StrictMode; + // TODO (StrictEffectsMode) Add support for new strict mode "level" attribute + mode |= StrictLegacyMode; 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..6f79f0331d348 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 { @@ -421,9 +424,18 @@ export function resetWorkInProgress(workInProgress: Fiber, renderLanes: Lanes) { export function createHostRootFiber(tag: RootTag): Fiber { let mode; if (tag === ConcurrentRoot) { - mode = ConcurrentMode | BlockingMode | StrictMode; + if (enableStrictEffects && createRootStrictEffectsByDefault) { + mode = + ConcurrentMode | BlockingMode | StrictLegacyMode | StrictEffectsMode; + } else { + mode = ConcurrentMode | BlockingMode | StrictLegacyMode; + } } else if (tag === BlockingRoot) { - mode = BlockingMode | StrictMode; + if (enableStrictEffects && createRootStrictEffectsByDefault) { + mode = BlockingMode | StrictLegacyMode | StrictEffectsMode; + } else { + mode = BlockingMode | StrictLegacyMode; + } } else { mode = NoMode; } @@ -472,7 +484,8 @@ export function createFiberFromTypeAndProps( break; case REACT_STRICT_MODE_TYPE: fiberTag = Mode; - mode |= StrictMode; + // TODO (StrictEffectsMode) Add support for new strict mode "level" attribute + mode |= StrictLegacyMode; 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..3b4edc51e28e8 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. ' + diff --git a/packages/react-reconciler/src/ReactFiberReconciler.old.js b/packages/react-reconciler/src/ReactFiberReconciler.old.js index 77032eb24b108..ecbcbd8a9e5e3 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. ' + diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index 918261a2e3562..ebc9fb8f17605 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,8 @@ import { } from './ReactFiber.new'; import { NoMode, - StrictMode, + StrictLegacyMode, + StrictEffectsMode, ProfileMode, BlockingMode, ConcurrentMode, @@ -2070,7 +2071,7 @@ function commitRootImpl(root, renderPriorityLevel) { legacyErrorBoundariesThatAlreadyFailed = null; } - if (__DEV__ && enableDoubleInvokingEffects) { + if (__DEV__ && enableStrictEffects) { if (!rootDidHavePassiveEffects) { commitDoubleInvokeEffectsInDEV(root.current, false); } @@ -2257,7 +2258,7 @@ function flushPassiveEffectsImpl() { markPassiveEffectsStopped(); } - if (__DEV__ && enableDoubleInvokingEffects) { + if (__DEV__ && enableStrictEffects) { commitDoubleInvokeEffectsInDEV(root.current, true); } @@ -2560,9 +2561,9 @@ function commitDoubleInvokeEffectsInDEV( fiber: Fiber, hasPassiveEffects: boolean, ) { - if (__DEV__ && enableDoubleInvokingEffects) { - // Never double-invoke effects for legacy roots. - if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) { + if (__DEV__ && enableStrictEffects) { + // Never double-invoke effects outside of StrictEffectsMode. + if ((fiber.mode & StrictEffectsMode) === NoMode) { return; } @@ -2589,9 +2590,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 +2935,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..25b77701d8699 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,8 @@ import { } from './ReactFiber.old'; import { NoMode, - StrictMode, + StrictLegacyMode, + StrictEffectsMode, ProfileMode, BlockingMode, ConcurrentMode, @@ -2070,7 +2071,7 @@ function commitRootImpl(root, renderPriorityLevel) { legacyErrorBoundariesThatAlreadyFailed = null; } - if (__DEV__ && enableDoubleInvokingEffects) { + if (__DEV__ && enableStrictEffects) { if (!rootDidHavePassiveEffects) { commitDoubleInvokeEffectsInDEV(root.current, false); } @@ -2257,7 +2258,7 @@ function flushPassiveEffectsImpl() { markPassiveEffectsStopped(); } - if (__DEV__ && enableDoubleInvokingEffects) { + if (__DEV__ && enableStrictEffects) { commitDoubleInvokeEffectsInDEV(root.current, true); } @@ -2560,9 +2561,9 @@ function commitDoubleInvokeEffectsInDEV( fiber: Fiber, hasPassiveEffects: boolean, ) { - if (__DEV__ && enableDoubleInvokingEffects) { - // Never double-invoke effects for legacy roots. - if ((fiber.mode & (BlockingMode | ConcurrentMode)) === NoMode) { + if (__DEV__ && enableStrictEffects) { + // Never double-invoke effects outside of StrictEffectsMode. + if ((fiber.mode & StrictEffectsMode) === NoMode) { return; } @@ -2589,9 +2590,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 +2935,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/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/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,