From 7df32c4c8c53d971e894c5e8d62a3cc908489b05 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 3 Feb 2020 12:30:01 -0800 Subject: [PATCH] Flush `useEffect` clean up functions in the passive effects phase (#17925) * Flush useEffect clean up functions in the passive effects phase This is a change in behavior that may cause broken product code, so it has been added behind a killswitch (deferPassiveEffectCleanupDuringUnmount) * Avoid scheduling unnecessary callbacks for cleanup effects Updated enqueuePendingPassiveEffectDestroyFn() to check rootDoesHavePassiveEffects before scheduling a new callback. This way we'll only schedule (at most) one. * Updated newly added test for added clarity. * Cleaned up hooks effect tags We previously used separate Mount* and Unmount* tags to track hooks work for each phase (snapshot, mutation, layout, and passive). This was somewhat complicated to trace through and there were man tag types we never even used (e.g. UnmountLayout, MountMutation, UnmountSnapshot). In addition to this, it left passive and layout hooks looking the same after renders without changed dependencies, which meant we were unable to reliably defer passive effect destroy functions until after the commit phase. This commit reduces the effect tag types to only include Layout and Passive and differentiates between work and no-work with an HasEffect flag. * Disabled deferred passive effects flushing in OSS builds for now * Split up unmount and mount effects list traversal --- .../src/ReactFiberCommitWork.js | 121 +++++++++++------- .../react-reconciler/src/ReactFiberHooks.js | 46 ++++--- .../src/ReactFiberWorkLoop.js | 31 +++++ .../src/ReactHookEffectTags.js | 16 +-- ...eactHooksWithNoopRenderer-test.internal.js | 104 +++++++++++++-- ...tSuspenseWithNoopRenderer-test.internal.js | 5 +- packages/shared/ReactFeatureFlags.js | 6 + .../forks/ReactFeatureFlags.native-fb.js | 1 + .../forks/ReactFeatureFlags.native-oss.js | 1 + .../forks/ReactFeatureFlags.persistent.js | 1 + .../forks/ReactFeatureFlags.test-renderer.js | 1 + .../ReactFeatureFlags.test-renderer.www.js | 1 + .../shared/forks/ReactFeatureFlags.www.js | 1 + 13 files changed, 248 insertions(+), 87 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js index 77b074a175222..f4282ac4fa5b5 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.js @@ -26,6 +26,7 @@ import type {ReactPriorityLevel} from './SchedulerWithReactIntegration'; import {unstable_wrap as Schedule_tracing_wrap} from 'scheduler/tracing'; import { + deferPassiveEffectCleanupDuringUnmount, enableSchedulerTracing, enableProfilerTimer, enableSuspenseServerRenderer, @@ -109,16 +110,13 @@ import { captureCommitPhaseError, resolveRetryThenable, markCommitTimeOfFallback, + enqueuePendingPassiveEffectDestroyFn, } from './ReactFiberWorkLoop'; import { NoEffect as NoHookEffect, - UnmountSnapshot, - UnmountMutation, - MountMutation, - UnmountLayout, - MountLayout, - UnmountPassive, - MountPassive, + HasEffect as HookHasEffect, + Layout as HookLayout, + Passive as HookPassive, } from './ReactHookEffectTags'; import {didWarnAboutReassigningProps} from './ReactFiberBeginWork'; import {runWithPriority, NormalPriority} from './SchedulerWithReactIntegration'; @@ -250,7 +248,6 @@ function commitBeforeMutationLifeCycles( case ForwardRef: case SimpleMemoComponent: case Chunk: { - commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork); return; } case ClassComponent: { @@ -328,18 +325,14 @@ function commitBeforeMutationLifeCycles( ); } -function commitHookEffectList( - unmountTag: number, - mountTag: number, - finishedWork: Fiber, -) { +function commitHookEffectListUnmount(tag: number, finishedWork: Fiber) { const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any); let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; if (lastEffect !== null) { const firstEffect = lastEffect.next; let effect = firstEffect; do { - if ((effect.tag & unmountTag) !== NoHookEffect) { + if ((effect.tag & tag) === tag) { // Unmount const destroy = effect.destroy; effect.destroy = undefined; @@ -347,7 +340,19 @@ function commitHookEffectList( destroy(); } } - if ((effect.tag & mountTag) !== NoHookEffect) { + effect = effect.next; + } while (effect !== firstEffect); + } +} + +function commitHookEffectListMount(tag: number, finishedWork: Fiber) { + const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any); + let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; + if (lastEffect !== null) { + const firstEffect = lastEffect.next; + let effect = firstEffect; + do { + if ((effect.tag & tag) === tag) { // Mount const create = effect.create; effect.destroy = create(); @@ -398,8 +403,11 @@ export function commitPassiveHookEffects(finishedWork: Fiber): void { case ForwardRef: case SimpleMemoComponent: case Chunk: { - commitHookEffectList(UnmountPassive, NoHookEffect, finishedWork); - commitHookEffectList(NoHookEffect, MountPassive, finishedWork); + // TODO (#17945) We should call all passive destroy functions (for all fibers) + // before calling any create functions. The current approach only serializes + // these for a single fiber. + commitHookEffectListUnmount(HookPassive | HookHasEffect, finishedWork); + commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork); break; } default: @@ -419,7 +427,11 @@ function commitLifeCycles( case ForwardRef: case SimpleMemoComponent: case Chunk: { - commitHookEffectList(UnmountLayout, MountLayout, finishedWork); + // At this point layout effects have already been destroyed (during mutation phase). + // This is done to prevent sibling component effects from interfering with each other, + // e.g. a destroy function in one component should never override a ref set + // by a create function in another component during the same commit. + commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); return; } case ClassComponent: { @@ -756,32 +768,47 @@ function commitUnmount( if (lastEffect !== null) { const firstEffect = lastEffect.next; - // When the owner fiber is deleted, the destroy function of a passive - // effect hook is called during the synchronous commit phase. This is - // a concession to implementation complexity. Calling it in the - // passive effect phase (like they usually are, when dependencies - // change during an update) would require either traversing the - // children of the deleted fiber again, or including unmount effects - // as part of the fiber effect list. - // - // Because this is during the sync commit phase, we need to change - // the priority. - // - // TODO: Reconsider this implementation trade off. - const priorityLevel = - renderPriorityLevel > NormalPriority - ? NormalPriority - : renderPriorityLevel; - runWithPriority(priorityLevel, () => { + if (deferPassiveEffectCleanupDuringUnmount) { let effect = firstEffect; do { - const destroy = effect.destroy; + const {destroy, tag} = effect; if (destroy !== undefined) { - safelyCallDestroy(current, destroy); + if ((tag & HookPassive) !== NoHookEffect) { + enqueuePendingPassiveEffectDestroyFn(destroy); + } else { + safelyCallDestroy(current, destroy); + } } effect = effect.next; } while (effect !== firstEffect); - }); + } else { + // When the owner fiber is deleted, the destroy function of a passive + // effect hook is called during the synchronous commit phase. This is + // a concession to implementation complexity. Calling it in the + // passive effect phase (like they usually are, when dependencies + // change during an update) would require either traversing the + // children of the deleted fiber again, or including unmount effects + // as part of the fiber effect list. + // + // Because this is during the sync commit phase, we need to change + // the priority. + // + // TODO: Reconsider this implementation trade off. + const priorityLevel = + renderPriorityLevel > NormalPriority + ? NormalPriority + : renderPriorityLevel; + runWithPriority(priorityLevel, () => { + let effect = firstEffect; + do { + const destroy = effect.destroy; + if (destroy !== undefined) { + safelyCallDestroy(current, destroy); + } + effect = effect.next; + } while (effect !== firstEffect); + }); + } } } return; @@ -1285,9 +1312,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void { case MemoComponent: case SimpleMemoComponent: case Chunk: { - // Note: We currently never use MountMutation, but useLayout uses - // UnmountMutation. - commitHookEffectList(UnmountMutation, MountMutation, finishedWork); + // Layout effects are destroyed during the mutation phase so that all + // destroy functions for all fibers are called before any create functions. + // This prevents sibling component effects from interfering with each other, + // e.g. a destroy function in one component should never override a ref set + // by a create function in another component during the same commit. + commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork); return; } case Profiler: { @@ -1325,9 +1355,12 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void { case MemoComponent: case SimpleMemoComponent: case Chunk: { - // Note: We currently never use MountMutation, but useLayout uses - // UnmountMutation. - commitHookEffectList(UnmountMutation, MountMutation, finishedWork); + // Layout effects are destroyed during the mutation phase so that all + // destroy functions for all fibers are called before any create functions. + // This prevents sibling component effects from interfering with each other, + // e.g. a destroy function in one component should never override a ref set + // by a create function in another component during the same commit. + commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork); return; } case ClassComponent: { diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index 02191ffe5d906..fdb0875f8bb5c 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -28,11 +28,9 @@ import { Passive as PassiveEffect, } from 'shared/ReactSideEffectTags'; import { - NoEffect as NoHookEffect, - UnmountMutation, - MountLayout, - UnmountPassive, - MountPassive, + HasEffect as HookHasEffect, + Layout as HookLayout, + Passive as HookPassive, } from './ReactHookEffectTags'; import { scheduleWork, @@ -923,7 +921,12 @@ function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void { const hook = mountWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; currentlyRenderingFiber.effectTag |= fiberEffectTag; - hook.memoizedState = pushEffect(hookEffectTag, create, undefined, nextDeps); + hook.memoizedState = pushEffect( + HookHasEffect | hookEffectTag, + create, + undefined, + nextDeps, + ); } function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void { @@ -937,7 +940,7 @@ function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void { if (nextDeps !== null) { const prevDeps = prevEffect.deps; if (areHookInputsEqual(nextDeps, prevDeps)) { - pushEffect(NoHookEffect, create, destroy, nextDeps); + pushEffect(hookEffectTag, create, destroy, nextDeps); return; } } @@ -945,7 +948,12 @@ function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps): void { currentlyRenderingFiber.effectTag |= fiberEffectTag; - hook.memoizedState = pushEffect(hookEffectTag, create, destroy, nextDeps); + hook.memoizedState = pushEffect( + HookHasEffect | hookEffectTag, + create, + destroy, + nextDeps, + ); } function mountEffect( @@ -960,7 +968,7 @@ function mountEffect( } return mountEffectImpl( UpdateEffect | PassiveEffect, - UnmountPassive | MountPassive, + HookPassive, create, deps, ); @@ -978,7 +986,7 @@ function updateEffect( } return updateEffectImpl( UpdateEffect | PassiveEffect, - UnmountPassive | MountPassive, + HookPassive, create, deps, ); @@ -988,24 +996,14 @@ function mountLayoutEffect( create: () => (() => void) | void, deps: Array | void | null, ): void { - return mountEffectImpl( - UpdateEffect, - UnmountMutation | MountLayout, - create, - deps, - ); + return mountEffectImpl(UpdateEffect, HookLayout, create, deps); } function updateLayoutEffect( create: () => (() => void) | void, deps: Array | void | null, ): void { - return updateEffectImpl( - UpdateEffect, - UnmountMutation | MountLayout, - create, - deps, - ); + return updateEffectImpl(UpdateEffect, HookLayout, create, deps); } function imperativeHandleEffect( @@ -1059,7 +1057,7 @@ function mountImperativeHandle( return mountEffectImpl( UpdateEffect, - UnmountMutation | MountLayout, + HookLayout, imperativeHandleEffect.bind(null, create, ref), effectDeps, ); @@ -1086,7 +1084,7 @@ function updateImperativeHandle( return updateEffectImpl( UpdateEffect, - UnmountMutation | MountLayout, + HookLayout, imperativeHandleEffect.bind(null, create, ref), effectDeps, ); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index a4664613ac224..4c067de698ec1 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -18,6 +18,7 @@ import type {Hook} from './ReactFiberHooks'; import { warnAboutDeprecatedLifecycles, + deferPassiveEffectCleanupDuringUnmount, enableUserTimingAPI, enableSuspenseServerRenderer, replayFailedUnitOfWorkWithInvokeGuardedCallback, @@ -257,6 +258,7 @@ let rootDoesHavePassiveEffects: boolean = false; let rootWithPendingPassiveEffects: FiberRoot | null = null; let pendingPassiveEffectsRenderPriority: ReactPriorityLevel = NoPriority; let pendingPassiveEffectsExpirationTime: ExpirationTime = NoWork; +let pendingUnmountedPassiveEffectDestroyFunctions: Array<() => void> = []; let rootsWithPendingDiscreteUpdates: Map< FiberRoot, @@ -2176,6 +2178,21 @@ export function flushPassiveEffects() { } } +export function enqueuePendingPassiveEffectDestroyFn( + destroy: () => void, +): void { + if (deferPassiveEffectCleanupDuringUnmount) { + pendingUnmountedPassiveEffectDestroyFunctions.push(destroy); + if (!rootDoesHavePassiveEffects) { + rootDoesHavePassiveEffects = true; + scheduleCallback(NormalPriority, () => { + flushPassiveEffects(); + return null; + }); + } + } +} + function flushPassiveEffectsImpl() { if (rootWithPendingPassiveEffects === null) { return false; @@ -2193,6 +2210,20 @@ function flushPassiveEffectsImpl() { executionContext |= CommitContext; const prevInteractions = pushInteractions(root); + if (deferPassiveEffectCleanupDuringUnmount) { + // Flush any pending passive effect destroy functions that belong to + // components that were unmounted during the most recent commit. + for ( + let i = 0; + i < pendingUnmountedPassiveEffectDestroyFunctions.length; + i++ + ) { + const destroy = pendingUnmountedPassiveEffectDestroyFunctions[i]; + invokeGuardedCallback(null, destroy, null); + } + pendingUnmountedPassiveEffectDestroyFunctions.length = 0; + } + // Note: This currently assumes there are no passive effects on the root // fiber, because the root is not part of its own effect list. This could // change in the future. diff --git a/packages/react-reconciler/src/ReactHookEffectTags.js b/packages/react-reconciler/src/ReactHookEffectTags.js index d54df30cf4e73..709b5b891e8fa 100644 --- a/packages/react-reconciler/src/ReactHookEffectTags.js +++ b/packages/react-reconciler/src/ReactHookEffectTags.js @@ -9,11 +9,11 @@ export type HookEffectTag = number; -export const NoEffect = /* */ 0b00000000; -export const UnmountSnapshot = /* */ 0b00000010; -export const UnmountMutation = /* */ 0b00000100; -export const MountMutation = /* */ 0b00001000; -export const UnmountLayout = /* */ 0b00010000; -export const MountLayout = /* */ 0b00100000; -export const MountPassive = /* */ 0b01000000; -export const UnmountPassive = /* */ 0b10000000; +export const NoEffect = /* */ 0b000; + +// Represents whether effect should fire. +export const HasEffect = /* */ 0b001; + +// Represents the phase in which the effect (not the clean-up) fires. +export const Layout = /* */ 0b010; +export const Passive = /* */ 0b100; diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js index ca4d3090215eb..c98dbb1171273 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js @@ -43,6 +43,7 @@ describe('ReactHooksWithNoopRenderer', () => { ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; ReactFeatureFlags.enableSchedulerTracing = true; ReactFeatureFlags.flushSuspenseFallbacksInTests = false; + ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount = true; React = require('react'); ReactNoop = require('react-noop-renderer'); Scheduler = require('scheduler'); @@ -972,6 +973,90 @@ describe('ReactHooksWithNoopRenderer', () => { }, ); + it('defers passive effect destroy functions during unmount', () => { + function Child({bar, foo}) { + React.useEffect(() => { + Scheduler.unstable_yieldValue('passive bar create'); + return () => { + Scheduler.unstable_yieldValue('passive bar destroy'); + }; + }, [bar]); + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('layout bar create'); + return () => { + Scheduler.unstable_yieldValue('layout bar destroy'); + }; + }, [bar]); + React.useEffect(() => { + Scheduler.unstable_yieldValue('passive foo create'); + return () => { + Scheduler.unstable_yieldValue('passive foo destroy'); + }; + }, [foo]); + React.useLayoutEffect(() => { + Scheduler.unstable_yieldValue('layout foo create'); + return () => { + Scheduler.unstable_yieldValue('layout foo destroy'); + }; + }, [foo]); + Scheduler.unstable_yieldValue('render'); + return null; + } + + act(() => { + ReactNoop.render(, () => + Scheduler.unstable_yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'render', + 'layout bar create', + 'layout foo create', + 'Sync effect', + ]); + // Effects are deferred until after the commit + expect(Scheduler).toFlushAndYield([ + 'passive bar create', + 'passive foo create', + ]); + }); + + // This update is exists to test an internal implementation detail: + // Effects without updating dependencies lose their layout/passive tag during an update. + act(() => { + ReactNoop.render(, () => + Scheduler.unstable_yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'render', + 'layout foo destroy', + 'layout foo create', + 'Sync effect', + ]); + // Effects are deferred until after the commit + expect(Scheduler).toFlushAndYield([ + 'passive foo destroy', + 'passive foo create', + ]); + }); + + // Unmount the component and verify that passive destroy functions are deferred until post-commit. + act(() => { + ReactNoop.render(null, () => + Scheduler.unstable_yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'layout bar destroy', + 'layout foo destroy', + 'Sync effect', + ]); + // Effects are deferred until after the commit + expect(Scheduler).toFlushAndYield([ + 'passive bar destroy', + 'passive foo destroy', + ]); + }); + }); + it('updates have async priority', () => { function Counter(props) { const [count, updateCount] = useState('(empty)'); @@ -1554,12 +1639,14 @@ describe('ReactHooksWithNoopRenderer', () => { 'Unmount B [0]', 'Mount A [1]', 'Oops!', - // Clean up effect A. There's no effect B to clean-up, because it - // never mounted. - 'Unmount A [1]', ]); expect(ReactNoop.getChildren()).toEqual([]); }); + expect(Scheduler).toHaveYielded([ + // Clean up effect A runs passively on unmount. + // There's no effect B to clean-up, because it never mounted. + 'Unmount A [1]', + ]); }); it('handles errors on unmount', () => { @@ -1599,13 +1686,12 @@ describe('ReactHooksWithNoopRenderer', () => { expect(Scheduler).toFlushAndYieldThrough(['Count: 1', 'Sync effect']); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); expect(() => ReactNoop.flushPassiveEffects()).toThrow('Oops'); - expect(Scheduler).toHaveYielded([ - 'Oops!', - // B unmounts even though an error was thrown in the previous effect - 'Unmount B [0]', - ]); - expect(ReactNoop.getChildren()).toEqual([]); + expect(Scheduler).toHaveYielded(['Oops!']); }); + // B unmounts even though an error was thrown in the previous effect + // B's destroy function runs later on unmount though, since it's passive + expect(Scheduler).toHaveYielded(['Unmount B [0]']); + expect(ReactNoop.getChildren()).toEqual([]); }); it('works with memo', () => { diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js index d4997de38f705..361d8c1467090 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js @@ -21,6 +21,7 @@ describe('ReactSuspenseWithNoopRenderer', () => { ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false; ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false; ReactFeatureFlags.flushSuspenseFallbacksInTests = false; + ReactFeatureFlags.deferPassiveEffectCleanupDuringUnmount = true; React = require('react'); Fragment = React.Fragment; ReactNoop = require('react-noop-renderer'); @@ -1617,8 +1618,8 @@ describe('ReactSuspenseWithNoopRenderer', () => { expect(Scheduler).toFlushAndYield([ 'B', 'Destroy Layout Effect [Loading...]', - 'Destroy Effect [Loading...]', 'Layout Effect [B]', + 'Destroy Effect [Loading...]', 'Effect [B]', ]); @@ -1654,9 +1655,9 @@ describe('ReactSuspenseWithNoopRenderer', () => { expect(Scheduler).toFlushAndYield([ 'B2', 'Destroy Layout Effect [Loading...]', - 'Destroy Effect [Loading...]', 'Destroy Layout Effect [B]', 'Layout Effect [B2]', + 'Destroy Effect [Loading...]', 'Destroy Effect [B]', 'Effect [B2]', ]); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index ead32bb435b40..4610a042b8ba1 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -91,6 +91,12 @@ export const enableTrustedTypesIntegration = false; // Flag to turn event.target and event.currentTarget in ReactNative from a reactTag to a component instance export const enableNativeTargetAsInstance = false; +// Controls behavior of deferred effect destroy functions during unmount. +// Previously these functions were run during commit (along with layout effects). +// Ideally we should delay these until after commit for performance reasons. +// This flag provides a killswitch if that proves to break existing code somehow. +export const deferPassiveEffectCleanupDuringUnmount = false; + // -------------------------- // Future APIs to be deprecated // -------------------------- diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js index 7ae4ab0e4113d..324c229ff9934 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-fb.js +++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js @@ -50,6 +50,7 @@ export const disableTextareaChildren = false; export const disableUnstableRenderSubtreeIntoContainer = false; export const warnUnstableRenderSubtreeIntoContainer = false; export const disableUnstableCreatePortal = false; +export const deferPassiveEffectCleanupDuringUnmount = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js index 241dad2cf7e3c..8359c153f984a 100644 --- a/packages/shared/forks/ReactFeatureFlags.native-oss.js +++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js @@ -45,6 +45,7 @@ export const disableTextareaChildren = false; export const disableUnstableRenderSubtreeIntoContainer = false; export const warnUnstableRenderSubtreeIntoContainer = false; export const disableUnstableCreatePortal = false; +export const deferPassiveEffectCleanupDuringUnmount = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.persistent.js b/packages/shared/forks/ReactFeatureFlags.persistent.js index a8bb6bf0aff56..c20f6e842a5e8 100644 --- a/packages/shared/forks/ReactFeatureFlags.persistent.js +++ b/packages/shared/forks/ReactFeatureFlags.persistent.js @@ -45,6 +45,7 @@ export const disableTextareaChildren = false; export const disableUnstableRenderSubtreeIntoContainer = false; export const warnUnstableRenderSubtreeIntoContainer = false; export const disableUnstableCreatePortal = false; +export const deferPassiveEffectCleanupDuringUnmount = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js index ae94c8f7f5e7b..65ca4e3e78dac 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js @@ -45,6 +45,7 @@ export const disableTextareaChildren = false; export const disableUnstableRenderSubtreeIntoContainer = false; export const warnUnstableRenderSubtreeIntoContainer = false; export const disableUnstableCreatePortal = false; +export const deferPassiveEffectCleanupDuringUnmount = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js index 60448b0097b0e..cf4fbda7c7c28 100644 --- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js +++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js @@ -43,6 +43,7 @@ export const disableTextareaChildren = false; export const disableUnstableRenderSubtreeIntoContainer = false; export const warnUnstableRenderSubtreeIntoContainer = false; export const disableUnstableCreatePortal = false; +export const deferPassiveEffectCleanupDuringUnmount = false; // Only used in www builds. export function addUserTimingListener() { diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js index cd3e59e5fe589..3b59401fcdc21 100644 --- a/packages/shared/forks/ReactFeatureFlags.www.js +++ b/packages/shared/forks/ReactFeatureFlags.www.js @@ -15,6 +15,7 @@ export const { debugRenderPhaseSideEffectsForStrictMode, disableInputAttributeSyncing, enableTrustedTypesIntegration, + deferPassiveEffectCleanupDuringUnmount, } = require('ReactFeatureFlags'); // In www, we have experimental support for gathering data