diff --git a/packages/react-reconciler/src/ReactFiber.js b/packages/react-reconciler/src/ReactFiber.js index 84ef41f4fb97b..15705b59787bf 100644 --- a/packages/react-reconciler/src/ReactFiber.js +++ b/packages/react-reconciler/src/ReactFiber.js @@ -39,6 +39,7 @@ import { enableDebugTracing, enableFloat, enableHostSingletons, + enableDO_NOT_USE_disableStrictPassiveEffect, } from 'shared/ReactFeatureFlags'; import {NoFlags, Placement, StaticMask} from './ReactFiberFlags'; import {ConcurrentRoot} from './ReactRootTags'; @@ -87,6 +88,7 @@ import { StrictLegacyMode, StrictEffectsMode, ConcurrentUpdatesByDefaultMode, + NoStrictPassiveEffectsMode, } from './ReactTypeOfMode'; import { REACT_FORWARD_REF_TYPE, @@ -539,6 +541,12 @@ export function createFiberFromTypeAndProps( if ((mode & ConcurrentMode) !== NoMode) { // Strict effects should never run on legacy roots mode |= StrictEffectsMode; + if ( + enableDO_NOT_USE_disableStrictPassiveEffect && + pendingProps.DO_NOT_USE_disableStrictPassiveEffect + ) { + mode |= NoStrictPassiveEffectsMode; + } } break; case REACT_PROFILER_TYPE: @@ -752,6 +760,10 @@ export function createFiberFromOffscreen( lanes: Lanes, key: null | string, ): Fiber { + if (__DEV__) { + // StrictMode in Offscreen should always run double passive effects + mode &= ~NoStrictPassiveEffectsMode; + } const fiber = createFiber(OffscreenComponent, pendingProps, key, mode); fiber.elementType = REACT_OFFSCREEN_TYPE; fiber.lanes = lanes; diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 7d528709b6e52..53e4b39abd9da 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -58,6 +58,7 @@ import { DebugTracingMode, StrictEffectsMode, StrictLegacyMode, + NoStrictPassiveEffectsMode, } from './ReactTypeOfMode'; import { NoLane, @@ -2257,7 +2258,8 @@ function mountEffect( ): void { if ( __DEV__ && - (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode + (currentlyRenderingFiber.mode & StrictEffectsMode) !== NoMode && + (currentlyRenderingFiber.mode & NoStrictPassiveEffectsMode) === NoMode ) { mountEffectImpl( MountPassiveDevEffect | PassiveEffect | PassiveStaticEffect, diff --git a/packages/react-reconciler/src/ReactTypeOfMode.js b/packages/react-reconciler/src/ReactTypeOfMode.js index 07e08cff210bd..6da450ffeac6d 100644 --- a/packages/react-reconciler/src/ReactTypeOfMode.js +++ b/packages/react-reconciler/src/ReactTypeOfMode.js @@ -9,11 +9,12 @@ export type TypeOfMode = number; -export const NoMode = /* */ 0b000000; +export const NoMode = /* */ 0b0000000; // TODO: Remove ConcurrentMode by reading from the root tag instead -export const ConcurrentMode = /* */ 0b000001; -export const ProfileMode = /* */ 0b000010; -export const DebugTracingMode = /* */ 0b000100; -export const StrictLegacyMode = /* */ 0b001000; -export const StrictEffectsMode = /* */ 0b010000; -export const ConcurrentUpdatesByDefaultMode = /* */ 0b100000; +export const ConcurrentMode = /* */ 0b0000001; +export const ProfileMode = /* */ 0b0000010; +export const DebugTracingMode = /* */ 0b0000100; +export const StrictLegacyMode = /* */ 0b0001000; +export const StrictEffectsMode = /* */ 0b0010000; +export const ConcurrentUpdatesByDefaultMode = /* */ 0b0100000; +export const NoStrictPassiveEffectsMode = /* */ 0b1000000; diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js index 129d4d0ddc8e0..f6ef02de522f9 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreenStrictMode-test.js @@ -55,6 +55,30 @@ describe('ReactOffscreenStrictMode', () => { ]); }); + // @gate __DEV__ && enableOffscreen + it('should trigger strict effects when disableStrictPassiveEffect is presented on StrictMode', async () => { + await act(() => { + ReactNoop.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 __DEV__ && enableOffscreen && useModernStrictMode it('should not trigger strict effects when offscreen is hidden', async () => { await act(() => { diff --git a/packages/react/src/__tests__/ReactStrictMode-test.internal.js b/packages/react/src/__tests__/ReactStrictMode-test.internal.js index 48efe5e7d5fd6..11ed37f599715 100644 --- a/packages/react/src/__tests__/ReactStrictMode-test.internal.js +++ b/packages/react/src/__tests__/ReactStrictMode-test.internal.js @@ -104,6 +104,28 @@ describe('ReactStrictMode', () => { ]); }); + // @gate enableDO_NOT_USE_disableStrictPassiveEffect + it('should include legacy + strict effects mode, but not strict passive effect with disableStrictPassiveEffect', async () => { + await act(() => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + root.render( + + + , + ); + }); + + expect(log).toEqual([ + 'A: render', + 'A: render', + 'A: useLayoutEffect mount', + 'A: useEffect mount', + 'A: useLayoutEffect unmount', + 'A: useLayoutEffect mount', + ]); + }); + it('should allow level to be increased with nesting', async () => { await act(() => { const container = document.createElement('div'); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 7cb339c83cc85..1236429619ae8 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -237,3 +237,4 @@ export const consoleManagedByDevToolsDuringStrictMode = true; // components will encounter in production, especially when used With . // TODO: clean up legacy once tests pass WWW. export const useModernStrictMode = false; +export const enableDO_NOT_USE_disableStrictPassiveEffect = false; diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index e96ce86b00286..0edbf1eea7072 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -82,6 +82,7 @@ export const enableFloat = true; export const enableHostSingletons = true; export const useModernStrictMode = false; +export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = false; export const diffInCommitPhase = true; diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 97556a05514ce..1ad75cefa4954 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -68,6 +68,7 @@ export const enableFloat = true; export const enableHostSingletons = true; export const useModernStrictMode = false; +export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = false; export const enableDeferRootSchedulingToMicrotask = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index 6437b4de07086..67d35fbdd1e85 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -68,6 +68,7 @@ export const enableFloat = true; export const enableHostSingletons = true; export const useModernStrictMode = false; +export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = false; export const enableDeferRootSchedulingToMicrotask = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js index 55535ce5e464d..0954b32e9af0b 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native.js @@ -66,6 +66,7 @@ export const enableFloat = true; export const enableHostSingletons = true; export const useModernStrictMode = false; +export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableDeferRootSchedulingToMicrotask = true; export const diffInCommitPhase = true; diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 1df2ff6b9eeea..00808854a4d2e 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -70,6 +70,7 @@ export const enableFloat = true; export const enableHostSingletons = true; export const useModernStrictMode = false; +export const enableDO_NOT_USE_disableStrictPassiveEffect = false; export const enableFizzExternalRuntime = false; export const enableDeferRootSchedulingToMicrotask = true; diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js index 58cfcdfff4214..a1197be405837 100644 --- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js +++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js @@ -28,6 +28,7 @@ export const enableDeferRootSchedulingToMicrotask = __VARIANT__; export const diffInCommitPhase = __VARIANT__; export const enableAsyncActions = __VARIANT__; export const alwaysThrottleRetries = __VARIANT__; +export const enableDO_NOT_USE_disableStrictPassiveEffect = __VARIANT__; // Enable this flag to help with concurrent mode debugging. // It logs information to the console about React scheduling, rendering, and commit phases. diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index 426a9ec8013e6..36b5236df8319 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -30,6 +30,7 @@ export const { diffInCommitPhase, enableAsyncActions, alwaysThrottleRetries, + enableDO_NOT_USE_disableStrictPassiveEffect, } = dynamicFeatureFlags; // On WWW, __EXPERIMENTAL__ is used for a new modern build.