diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
index 7d4220460d93..bd0c6107f373 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
@@ -6509,7 +6509,6 @@ describe('ReactDOMFizzServer', () => {
});
describe('useEffectEvent', () => {
- // @gate enableUseEffectEventHook
it('can server render a component with useEffectEvent', async () => {
const ref = React.createRef();
function App() {
@@ -6540,7 +6539,6 @@ describe('ReactDOMFizzServer', () => {
expect(getVisibleChildren(container)).toEqual();
});
- // @gate enableUseEffectEventHook
it('throws if useEffectEvent is called during a server render', async () => {
const logs = [];
function App() {
@@ -6572,7 +6570,6 @@ describe('ReactDOMFizzServer', () => {
expect(reportedServerErrors).toEqual([caughtError]);
});
- // @gate enableUseEffectEventHook
it('does not guarantee useEffectEvent return values during server rendering are distinct', async () => {
function App() {
const onClick1 = React.useEffectEvent(() => {});
diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.js b/packages/react-reconciler/src/ReactFiberCommitWork.js
index ecacb0c15850..946e758c1dce 100644
--- a/packages/react-reconciler/src/ReactFiberCommitWork.js
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.js
@@ -47,6 +47,7 @@ import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
import {
alwaysThrottleRetries,
enableCreateEventHandleAPI,
+ enableEffectEventMutationPhase,
enableHiddenSubtreeInsertionEffectCleanup,
enableProfilerTimer,
enableProfilerCommitHooks,
@@ -54,7 +55,6 @@ import {
enableScopeAPI,
enableUpdaterTracking,
enableTransitionTracing,
- enableUseEffectEventHook,
enableLegacyHidden,
disableLegacyMode,
enableComponentPerformanceTrack,
@@ -500,17 +500,14 @@ function commitBeforeMutationEffectsOnFiber(
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
- if (enableUseEffectEventHook) {
- if ((flags & Update) !== NoFlags) {
- const updateQueue: FunctionComponentUpdateQueue | null =
- (finishedWork.updateQueue: any);
- const eventPayloads =
- updateQueue !== null ? updateQueue.events : null;
- if (eventPayloads !== null) {
- for (let ii = 0; ii < eventPayloads.length; ii++) {
- const {ref, nextImpl} = eventPayloads[ii];
- ref.impl = nextImpl;
- }
+ if (!enableEffectEventMutationPhase && (flags & Update) !== NoFlags) {
+ const updateQueue: FunctionComponentUpdateQueue | null =
+ (finishedWork.updateQueue: any);
+ const eventPayloads = updateQueue !== null ? updateQueue.events : null;
+ if (eventPayloads !== null) {
+ for (let ii = 0; ii < eventPayloads.length; ii++) {
+ const {ref, nextImpl} = eventPayloads[ii];
+ ref.impl = nextImpl;
}
}
}
@@ -2050,6 +2047,19 @@ function commitMutationEffectsOnFiber(
commitReconciliationEffects(finishedWork, lanes);
if (flags & Update) {
+ // Mutate event effect callbacks before insertion effects.
+ if (enableEffectEventMutationPhase) {
+ const updateQueue: FunctionComponentUpdateQueue | null =
+ (finishedWork.updateQueue: any);
+ const eventPayloads =
+ updateQueue !== null ? updateQueue.events : null;
+ if (eventPayloads !== null) {
+ for (let ii = 0; ii < eventPayloads.length; ii++) {
+ const {ref, nextImpl} = eventPayloads[ii];
+ ref.impl = nextImpl;
+ }
+ }
+ }
commitHookEffectListUnmount(
HookInsertion | HookHasEffect,
finishedWork,
diff --git a/packages/react-reconciler/src/ReactFiberFlags.js b/packages/react-reconciler/src/ReactFiberFlags.js
index 7fa1dcb8173b..9f85897fb05c 100644
--- a/packages/react-reconciler/src/ReactFiberFlags.js
+++ b/packages/react-reconciler/src/ReactFiberFlags.js
@@ -9,7 +9,7 @@
import {
enableCreateEventHandleAPI,
- enableUseEffectEventHook,
+ enableEffectEventMutationPhase,
} from 'shared/ReactFeatureFlags';
export type Flags = number;
@@ -102,12 +102,11 @@ export const BeforeMutationMask: number =
// TODO: Only need to visit Deletions during BeforeMutation phase if an
// element is focused.
Update | ChildDeletion | Visibility
- : enableUseEffectEventHook
- ? // TODO: The useEffectEvent hook uses the snapshot phase for clean up but it
- // really should use the mutation phase for this or at least schedule an
- // explicit Snapshot phase flag for this.
- Update
- : 0);
+ : // useEffectEvent uses the snapshot phase,
+ // but we're moving it to the mutation phase.
+ enableEffectEventMutationPhase
+ ? 0
+ : Update);
// For View Transition support we use the snapshot phase to scan the tree for potentially
// affected ViewTransition components.
diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js
index 422667c6a122..0450495ff0d4 100644
--- a/packages/react-reconciler/src/ReactFiberHooks.js
+++ b/packages/react-reconciler/src/ReactFiberHooks.js
@@ -38,7 +38,6 @@ import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
enableSchedulingProfiler,
enableTransitionTracing,
- enableUseEffectEventHook,
enableLegacyCache,
disableLegacyMode,
enableNoCloningMemoCache,
@@ -3893,10 +3892,8 @@ export const ContextOnlyDispatcher: Dispatcher = {
useOptimistic: throwInvalidHookError,
useMemoCache: throwInvalidHookError,
useCacheRefresh: throwInvalidHookError,
+ useEffectEvent: throwInvalidHookError,
};
-if (enableUseEffectEventHook) {
- (ContextOnlyDispatcher: Dispatcher).useEffectEvent = throwInvalidHookError;
-}
const HooksDispatcherOnMount: Dispatcher = {
readContext,
@@ -3923,10 +3920,8 @@ const HooksDispatcherOnMount: Dispatcher = {
useOptimistic: mountOptimistic,
useMemoCache,
useCacheRefresh: mountRefresh,
+ useEffectEvent: mountEvent,
};
-if (enableUseEffectEventHook) {
- (HooksDispatcherOnMount: Dispatcher).useEffectEvent = mountEvent;
-}
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
@@ -3953,10 +3948,8 @@ const HooksDispatcherOnUpdate: Dispatcher = {
useOptimistic: updateOptimistic,
useMemoCache,
useCacheRefresh: updateRefresh,
+ useEffectEvent: updateEvent,
};
-if (enableUseEffectEventHook) {
- (HooksDispatcherOnUpdate: Dispatcher).useEffectEvent = updateEvent;
-}
const HooksDispatcherOnRerender: Dispatcher = {
readContext,
@@ -3983,10 +3976,8 @@ const HooksDispatcherOnRerender: Dispatcher = {
useOptimistic: rerenderOptimistic,
useMemoCache,
useCacheRefresh: updateRefresh,
+ useEffectEvent: updateEvent,
};
-if (enableUseEffectEventHook) {
- (HooksDispatcherOnRerender: Dispatcher).useEffectEvent = updateEvent;
-}
let HooksDispatcherOnMountInDEV: Dispatcher | null = null;
let HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher | null = null;
@@ -4176,17 +4167,14 @@ if (__DEV__) {
mountHookTypesDev();
return mountRefresh();
},
+ useEffectEvent) => Return>(
+ callback: F,
+ ): F {
+ currentHookNameInDev = 'useEffectEvent';
+ mountHookTypesDev();
+ return mountEvent(callback);
+ },
};
- if (enableUseEffectEventHook) {
- (HooksDispatcherOnMountInDEV: Dispatcher).useEffectEvent =
- function useEffectEvent) => Return>(
- callback: F,
- ): F {
- currentHookNameInDev = 'useEffectEvent';
- mountHookTypesDev();
- return mountEvent(callback);
- };
- }
HooksDispatcherOnMountWithHookTypesInDEV = {
readContext(context: ReactContext): T {
@@ -4343,17 +4331,14 @@ if (__DEV__) {
updateHookTypesDev();
return mountRefresh();
},
+ useEffectEvent) => Return>(
+ callback: F,
+ ): F {
+ currentHookNameInDev = 'useEffectEvent';
+ updateHookTypesDev();
+ return mountEvent(callback);
+ },
};
- if (enableUseEffectEventHook) {
- (HooksDispatcherOnMountWithHookTypesInDEV: Dispatcher).useEffectEvent =
- function useEffectEvent) => Return>(
- callback: F,
- ): F {
- currentHookNameInDev = 'useEffectEvent';
- updateHookTypesDev();
- return mountEvent(callback);
- };
- }
HooksDispatcherOnUpdateInDEV = {
readContext(context: ReactContext): T {
@@ -4510,17 +4495,14 @@ if (__DEV__) {
updateHookTypesDev();
return updateRefresh();
},
+ useEffectEvent) => Return>(
+ callback: F,
+ ): F {
+ currentHookNameInDev = 'useEffectEvent';
+ updateHookTypesDev();
+ return updateEvent(callback);
+ },
};
- if (enableUseEffectEventHook) {
- (HooksDispatcherOnUpdateInDEV: Dispatcher).useEffectEvent =
- function useEffectEvent) => Return>(
- callback: F,
- ): F {
- currentHookNameInDev = 'useEffectEvent';
- updateHookTypesDev();
- return updateEvent(callback);
- };
- }
HooksDispatcherOnRerenderInDEV = {
readContext(context: ReactContext): T {
@@ -4677,17 +4659,14 @@ if (__DEV__) {
updateHookTypesDev();
return updateRefresh();
},
+ useEffectEvent) => Return>(
+ callback: F,
+ ): F {
+ currentHookNameInDev = 'useEffectEvent';
+ updateHookTypesDev();
+ return updateEvent(callback);
+ },
};
- if (enableUseEffectEventHook) {
- (HooksDispatcherOnRerenderInDEV: Dispatcher).useEffectEvent =
- function useEffectEvent) => Return>(
- callback: F,
- ): F {
- currentHookNameInDev = 'useEffectEvent';
- updateHookTypesDev();
- return updateEvent(callback);
- };
- }
InvalidNestedHooksDispatcherOnMountInDEV = {
readContext(context: ReactContext): T {
@@ -4868,18 +4847,15 @@ if (__DEV__) {
mountHookTypesDev();
return mountRefresh();
},
+ useEffectEvent) => Return>(
+ callback: F,
+ ): F {
+ currentHookNameInDev = 'useEffectEvent';
+ warnInvalidHookAccess();
+ mountHookTypesDev();
+ return mountEvent(callback);
+ },
};
- if (enableUseEffectEventHook) {
- (InvalidNestedHooksDispatcherOnMountInDEV: Dispatcher).useEffectEvent =
- function useEffectEvent) => Return>(
- callback: F,
- ): F {
- currentHookNameInDev = 'useEffectEvent';
- warnInvalidHookAccess();
- mountHookTypesDev();
- return mountEvent(callback);
- };
- }
InvalidNestedHooksDispatcherOnUpdateInDEV = {
readContext(context: ReactContext): T {
@@ -5060,18 +5036,15 @@ if (__DEV__) {
updateHookTypesDev();
return updateRefresh();
},
+ useEffectEvent) => Return>(
+ callback: F,
+ ): F {
+ currentHookNameInDev = 'useEffectEvent';
+ warnInvalidHookAccess();
+ updateHookTypesDev();
+ return updateEvent(callback);
+ },
};
- if (enableUseEffectEventHook) {
- (InvalidNestedHooksDispatcherOnUpdateInDEV: Dispatcher).useEffectEvent =
- function useEffectEvent) => Return>(
- callback: F,
- ): F {
- currentHookNameInDev = 'useEffectEvent';
- warnInvalidHookAccess();
- updateHookTypesDev();
- return updateEvent(callback);
- };
- }
InvalidNestedHooksDispatcherOnRerenderInDEV = {
readContext(context: ReactContext): T {
@@ -5252,16 +5225,13 @@ if (__DEV__) {
updateHookTypesDev();
return updateRefresh();
},
+ useEffectEvent) => Return>(
+ callback: F,
+ ): F {
+ currentHookNameInDev = 'useEffectEvent';
+ warnInvalidHookAccess();
+ updateHookTypesDev();
+ return updateEvent(callback);
+ },
};
- if (enableUseEffectEventHook) {
- (InvalidNestedHooksDispatcherOnRerenderInDEV: Dispatcher).useEffectEvent =
- function useEffectEvent) => Return>(
- callback: F,
- ): F {
- currentHookNameInDev = 'useEffectEvent';
- warnInvalidHookAccess();
- updateHookTypesDev();
- return updateEvent(callback);
- };
- }
}
diff --git a/packages/react-reconciler/src/ReactInternalTypes.js b/packages/react-reconciler/src/ReactInternalTypes.js
index ce22050123c7..d12c1e38694a 100644
--- a/packages/react-reconciler/src/ReactInternalTypes.js
+++ b/packages/react-reconciler/src/ReactInternalTypes.js
@@ -409,8 +409,7 @@ export type Dispatcher = {
create: () => (() => void) | void,
deps: Array | void | null,
): void,
- // TODO: Non-nullable once `enableUseEffectEventHook` is on everywhere.
- useEffectEvent?: ) => mixed>(callback: F) => F,
+ useEffectEvent: ) => mixed>(callback: F) => F,
useInsertionEffect(
create: () => (() => void) | void,
deps: Array | void | null,
diff --git a/packages/react-reconciler/src/__tests__/useEffectEvent-test.js b/packages/react-reconciler/src/__tests__/useEffectEvent-test.js
index f263c9af2693..0d903d59e4ac 100644
--- a/packages/react-reconciler/src/__tests__/useEffectEvent-test.js
+++ b/packages/react-reconciler/src/__tests__/useEffectEvent-test.js
@@ -53,7 +53,6 @@ describe('useEffectEvent', () => {
return ;
}
- // @gate enableUseEffectEventHook
it('memoizes basic case correctly', async () => {
class IncrementButton extends React.PureComponent {
increment = () => {
@@ -129,7 +128,6 @@ describe('useEffectEvent', () => {
);
});
- // @gate enableUseEffectEventHook
it('can be defined more than once', async () => {
class IncrementButton extends React.PureComponent {
increment = () => {
@@ -191,7 +189,6 @@ describe('useEffectEvent', () => {
);
});
- // @gate enableUseEffectEventHook
it('does not preserve `this` in event functions', async () => {
class GreetButton extends React.PureComponent {
greet = () => {
@@ -241,7 +238,6 @@ describe('useEffectEvent', () => {
);
});
- // @gate enableUseEffectEventHook
it('throws when called in render', async () => {
class IncrementButton extends React.PureComponent {
increment = () => {
@@ -275,7 +271,6 @@ describe('useEffectEvent', () => {
assertLog([]);
});
- // @gate enableUseEffectEventHook
it("useLayoutEffect shouldn't re-fire when event handlers change", async () => {
class IncrementButton extends React.PureComponent {
increment = () => {
@@ -375,7 +370,6 @@ describe('useEffectEvent', () => {
);
});
- // @gate enableUseEffectEventHook
it("useEffect shouldn't re-fire when event handlers change", async () => {
class IncrementButton extends React.PureComponent {
increment = () => {
@@ -474,7 +468,6 @@ describe('useEffectEvent', () => {
);
});
- // @gate enableUseEffectEventHook
it('is stable in a custom hook', async () => {
class IncrementButton extends React.PureComponent {
increment = () => {
@@ -579,7 +572,6 @@ describe('useEffectEvent', () => {
);
});
- // @gate enableUseEffectEventHook
it('is mutated before all other effects', async () => {
function Counter({value}) {
useInsertionEffect(() => {
@@ -603,7 +595,214 @@ describe('useEffectEvent', () => {
assertLog(['Effect value: 2', 'Event value: 2']);
});
- // @gate enableUseEffectEventHook
+ it('updates parent and child event effects before their respective effect lifecycles', async () => {
+ function Parent({value}) {
+ const parentEvent = useEffectEvent(() => {
+ Scheduler.log('Parent event: ' + value);
+ });
+
+ useInsertionEffect(() => {
+ Scheduler.log('Parent insertion');
+ parentEvent();
+ }, [value]);
+
+ return ;
+ }
+
+ function Child({value}) {
+ const childEvent = useEffectEvent(() => {
+ Scheduler.log('Child event: ' + value);
+ });
+
+ useInsertionEffect(() => {
+ Scheduler.log('Child insertion');
+ childEvent();
+ }, [value]);
+
+ return null;
+ }
+
+ ReactNoop.render();
+ await waitForAll([
+ 'Child insertion',
+ 'Child event: 1',
+ 'Parent insertion',
+ 'Parent event: 1',
+ ]);
+
+ await act(() => ReactNoop.render());
+ // Each component's event is updated before its own insertion effect runs
+ assertLog([
+ 'Child insertion',
+ 'Child event: 2',
+ 'Parent insertion',
+ 'Parent event: 2',
+ ]);
+ });
+
+ it('fires all insertion effects (interleaved) with useEffectEvent before firing any layout effects', async () => {
+ // This test mirrors the 'fires all insertion effects (interleaved) before firing any layout effects'
+ // test in ReactHooksWithNoopRenderer-test.js, but adds useEffectEvent to verify that
+ // event payloads are updated before each component's insertion effects run.
+ // It also includes passive effects to verify the full effect lifecycle.
+ let committedA = '(empty)';
+ let committedB = '(empty)';
+
+ function CounterA(props) {
+ const onEvent = useEffectEvent(() => {
+ return `Event A [A: ${committedA}, B: ${committedB}]`;
+ });
+
+ useInsertionEffect(() => {
+ // Call the event function to verify it sees the latest value
+ Scheduler.log(
+ `Create Insertion A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ committedA = String(props.count);
+ return () => {
+ Scheduler.log(
+ `Destroy Insertion A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ };
+ });
+
+ useLayoutEffect(() => {
+ Scheduler.log(
+ `Create Layout A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ return () => {
+ Scheduler.log(
+ `Destroy Layout A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ };
+ });
+
+ useEffect(() => {
+ Scheduler.log(
+ `Create Passive A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ return () => {
+ Scheduler.log(
+ `Destroy Passive A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ };
+ });
+
+ return null;
+ }
+
+ function CounterB(props) {
+ const onEvent = useEffectEvent(() => {
+ return `Event B [A: ${committedA}, B: ${committedB}]`;
+ });
+
+ useInsertionEffect(() => {
+ // Call the event function to verify it sees the latest value
+ Scheduler.log(
+ `Create Insertion B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ committedB = String(props.count);
+ return () => {
+ Scheduler.log(
+ `Destroy Insertion B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ };
+ });
+
+ useLayoutEffect(() => {
+ Scheduler.log(
+ `Create Layout B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ return () => {
+ Scheduler.log(
+ `Destroy Layout B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ };
+ });
+
+ useEffect(() => {
+ Scheduler.log(
+ `Create Passive B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ return () => {
+ Scheduler.log(
+ `Destroy Passive B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
+ );
+ };
+ });
+
+ return null;
+ }
+
+ await act(async () => {
+ ReactNoop.render(
+
+
+
+ ,
+ );
+ // All insertion effects fire before all layout effects, then passive effects
+ // Event functions should see the state AT THE TIME they're called
+ await waitForAll([
+ // Insertion effects (mutation phase)
+ 'Create Insertion A [A: (empty), B: (empty)], event: Event A [A: (empty), B: (empty)]',
+ 'Create Insertion B [A: 0, B: (empty)], event: Event B [A: 0, B: (empty)]',
+ // Layout effects
+ 'Create Layout A [A: 0, B: 0], event: Event A [A: 0, B: 0]',
+ 'Create Layout B [A: 0, B: 0], event: Event B [A: 0, B: 0]',
+ // Passive effects
+ 'Create Passive A [A: 0, B: 0], event: Event A [A: 0, B: 0]',
+ 'Create Passive B [A: 0, B: 0], event: Event B [A: 0, B: 0]',
+ ]);
+ expect([committedA, committedB]).toEqual(['0', '0']);
+ });
+
+ await act(async () => {
+ ReactNoop.render(
+
+
+
+ ,
+ );
+ await waitForAll([
+ // Component A: insertion destroy, then create
+ 'Destroy Insertion A [A: 0, B: 0], event: Event A [A: 0, B: 0]',
+ 'Create Insertion A [A: 0, B: 0], event: Event A [A: 0, B: 0]',
+ // Component A: layout destroy (after insertion updated committedA)
+ 'Destroy Layout A [A: 1, B: 0], event: Event A [A: 1, B: 0]',
+ // Component B: insertion destroy, then create
+ 'Destroy Insertion B [A: 1, B: 0], event: Event B [A: 1, B: 0]',
+ 'Create Insertion B [A: 1, B: 0], event: Event B [A: 1, B: 0]',
+ // Component B: layout destroy (after insertion updated committedB)
+ 'Destroy Layout B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
+ // Layout creates
+ 'Create Layout A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
+ 'Create Layout B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
+ // Passive destroys then creates
+ 'Destroy Passive A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
+ 'Destroy Passive B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
+ 'Create Passive A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
+ 'Create Passive B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
+ ]);
+ expect([committedA, committedB]).toEqual(['1', '1']);
+ });
+
+ // Unmount everything
+ await act(async () => {
+ ReactNoop.render(null);
+ await waitForAll([
+ // Insertion and layout destroys (mutation/layout phase)
+ 'Destroy Insertion A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
+ 'Destroy Layout A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
+ 'Destroy Insertion B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
+ 'Destroy Layout B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
+ // Passive destroys
+ 'Destroy Passive A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
+ 'Destroy Passive B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
+ ]);
+ });
+ });
+
it("doesn't provide a stable identity", async () => {
function Counter({shouldRender, value}) {
const onClick = useEffectEvent(() => {
@@ -642,7 +841,6 @@ describe('useEffectEvent', () => {
]);
});
- // @gate enableUseEffectEventHook
it('event handlers always see the latest committed value', async () => {
let committedEventHandler = null;
@@ -692,7 +890,6 @@ describe('useEffectEvent', () => {
expect(committedEventHandler()).toBe('Value seen by useEffectEvent: 2');
});
- // @gate enableUseEffectEventHook
it('integration: implements docs chat room example', async () => {
function createConnection() {
let connectedCallback;
@@ -781,7 +978,6 @@ describe('useEffectEvent', () => {
);
});
- // @gate enableUseEffectEventHook
it('integration: implements the docs logVisit example', async () => {
class AddToCartButton extends React.PureComponent {
addToCart = () => {
diff --git a/packages/react-server/src/ReactFizzHooks.js b/packages/react-server/src/ReactFizzHooks.js
index 119c2a0778fc..24e676fc7108 100644
--- a/packages/react-server/src/ReactFizzHooks.js
+++ b/packages/react-server/src/ReactFizzHooks.js
@@ -38,7 +38,6 @@ import {
} from './ReactFizzConfig';
import {createFastHash} from './ReactServerStreamConfig';
-import {enableUseEffectEventHook} from 'shared/ReactFeatureFlags';
import is from 'shared/objectIs';
import {
REACT_CONTEXT_TYPE,
@@ -833,6 +832,7 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs
useHostTransitionStatus,
useMemoCache,
useCacheRefresh,
+ useEffectEvent,
}
: {
readContext,
@@ -858,12 +858,9 @@ export const HooksDispatcher: Dispatcher = supportsClientAPIs
useOptimistic,
useMemoCache,
useCacheRefresh,
+ useEffectEvent,
};
-if (enableUseEffectEventHook) {
- HooksDispatcher.useEffectEvent = useEffectEvent;
-}
-
export let currentResumableState: null | ResumableState = (null: any);
export function setCurrentResumableState(
resumableState: null | ResumableState,
diff --git a/packages/react-server/src/ReactFlightHooks.js b/packages/react-server/src/ReactFlightHooks.js
index ed369be0e9b1..d814de462982 100644
--- a/packages/react-server/src/ReactFlightHooks.js
+++ b/packages/react-server/src/ReactFlightHooks.js
@@ -17,7 +17,6 @@ import {
} from 'shared/ReactSymbols';
import {createThenableState, trackUsedThenable} from './ReactFlightThenable';
import {isClientReference} from './ReactFlightServerConfig';
-import {enableUseEffectEventHook} from 'shared/ReactFeatureFlags';
let currentRequest = null;
let thenableIndexCounter = 0;
@@ -101,10 +100,8 @@ export const HooksDispatcher: Dispatcher = {
useCacheRefresh(): (?() => T, ?T) => void {
return unsupportedRefresh;
},
+ useEffectEvent: (unsupportedHook: any),
};
-if (enableUseEffectEventHook) {
- HooksDispatcher.useEffectEvent = (unsupportedHook: any);
-}
function unsupportedHook(): void {
throw new Error('This Hook is not supported in Server Components.');
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index ebb287568af8..3e68d8c89913 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -118,8 +118,6 @@ export const enableCPUSuspense = __EXPERIMENTAL__;
// Test this at Meta before enabling.
export const enableNoCloningMemoCache: boolean = false;
-export const enableUseEffectEventHook: boolean = true;
-
// Test in www before enabling in open source.
// Enables DOM-server to stream its instruction set as data-attributes
// (handled with an MutationObserver) instead of inline-scripts
@@ -127,6 +125,10 @@ export const enableFizzExternalRuntime = __EXPERIMENTAL__;
export const alwaysThrottleRetries: boolean = true;
+// Gate whether useEffectEvent uses the mutation phase (true) or before-mutation
+// phase (false) for updating event function references.
+export const enableEffectEventMutationPhase: boolean = false;
+
export const passChildrenWhenCloningPersistedNodes: boolean = false;
export const enableEagerAlternateStateNodeCleanup: boolean = true;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
index f139a3fcf288..389d46be4974 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
@@ -27,3 +27,4 @@ export const enableFragmentRefs = __VARIANT__;
export const enableFragmentRefsScrollIntoView = __VARIANT__;
export const enableFragmentRefsInstanceHandles = __VARIANT__;
export const enableComponentPerformanceTrack = __VARIANT__;
+export const enableEffectEventMutationPhase = __VARIANT__;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-fb.js b/packages/shared/forks/ReactFeatureFlags.native-fb.js
index d9a91f8a808f..cb0b0b66f600 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-fb.js
@@ -20,6 +20,7 @@ const dynamicFlags: DynamicExportsType = (dynamicFlagsUntyped: any);
// the exports object every time a flag is read.
export const {
alwaysThrottleRetries,
+ enableEffectEventMutationPhase,
enableHiddenSubtreeInsertionEffectCleanup,
enableObjectFiber,
enableEagerAlternateStateNodeCleanup,
@@ -64,7 +65,6 @@ export const enableTaint: boolean = true;
export const enableTransitionTracing: boolean = false;
export const enableTrustedTypesIntegration: boolean = false;
export const enableUpdaterTracking: boolean = __PROFILE__;
-export const enableUseEffectEventHook: boolean = true;
export const retryLaneExpirationMs = 5000;
export const syncLaneExpirationMs = 250;
export const transitionLaneExpirationMs = 5000;
diff --git a/packages/shared/forks/ReactFeatureFlags.native-oss.js b/packages/shared/forks/ReactFeatureFlags.native-oss.js
index fa8f336c03f1..f1e697b7b215 100644
--- a/packages/shared/forks/ReactFeatureFlags.native-oss.js
+++ b/packages/shared/forks/ReactFeatureFlags.native-oss.js
@@ -46,12 +46,12 @@ export const enableSchedulingProfiler: boolean =
!enableComponentPerformanceTrack && __PROFILE__;
export const enableScopeAPI: boolean = false;
export const enableEagerAlternateStateNodeCleanup: boolean = true;
+export const enableEffectEventMutationPhase: boolean = false;
export const enableSuspenseAvoidThisFallback: boolean = false;
export const enableSuspenseCallback: boolean = false;
export const enableTaint: boolean = true;
export const enableTransitionTracing: boolean = false;
export const enableTrustedTypesIntegration: boolean = false;
-export const enableUseEffectEventHook: boolean = true;
export const passChildrenWhenCloningPersistedNodes: boolean = false;
export const renameElementSymbol: boolean = true;
export const retryLaneExpirationMs = 5000;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index acf3847bd065..8e65aea17b2f 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -32,7 +32,6 @@ export const disableTextareaChildren: boolean = false;
export const enableSuspenseAvoidThisFallback: boolean = false;
export const enableCPUSuspense: boolean = false;
export const enableNoCloningMemoCache: boolean = false;
-export const enableUseEffectEventHook: boolean = true;
export const enableLegacyFBSupport: boolean = false;
export const enableMoveBefore: boolean = false;
export const enableHiddenSubtreeInsertionEffectCleanup: boolean = false;
@@ -59,6 +58,7 @@ export const enableInfiniteRenderLoopDetection: boolean = false;
export const renameElementSymbol: boolean = true;
export const enableEagerAlternateStateNodeCleanup: boolean = true;
+export const enableEffectEventMutationPhase: boolean = false;
export const enableYieldingBeforePassive: boolean = true;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
index 5d3a55130182..120a20160f8e 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
@@ -43,13 +43,13 @@ export const enableComponentPerformanceTrack = false;
export const enablePerformanceIssueReporting = false;
export const enableScopeAPI = false;
export const enableEagerAlternateStateNodeCleanup = true;
+export const enableEffectEventMutationPhase = false;
export const enableSuspenseAvoidThisFallback = false;
export const enableSuspenseCallback = false;
export const enableTaint = true;
export const enableTransitionTracing = false;
export const enableTrustedTypesIntegration = false;
export const enableUpdaterTracking = false;
-export const enableUseEffectEventHook = true;
export const passChildrenWhenCloningPersistedNodes = false;
export const renameElementSymbol = false;
export const retryLaneExpirationMs = 5000;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
index 553be202c45e..dee7642449a5 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
@@ -34,7 +34,6 @@ export const disableTextareaChildren: boolean = false;
export const enableSuspenseAvoidThisFallback: boolean = true;
export const enableCPUSuspense: boolean = false;
export const enableNoCloningMemoCache: boolean = false;
-export const enableUseEffectEventHook: boolean = true;
export const enableLegacyFBSupport: boolean = false;
export const enableMoveBefore: boolean = false;
export const enableHiddenSubtreeInsertionEffectCleanup: boolean = true;
@@ -65,6 +64,7 @@ export const renameElementSymbol: boolean = false;
export const enableObjectFiber: boolean = false;
export const enableEagerAlternateStateNodeCleanup: boolean = true;
+export const enableEffectEventMutationPhase: boolean = false;
export const enableHydrationLaneScheduling: boolean = true;
diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
index 1d174609cb9c..206e9ec3a369 100644
--- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
@@ -40,6 +40,8 @@ export const enableAsyncDebugInfo: boolean = __VARIANT__;
export const enableInternalInstanceMap: boolean = __VARIANT__;
+export const enableEffectEventMutationPhase: boolean = __VARIANT__;
+
// TODO: These flags are hard-coded to the default values used in open source.
// Update the tests so that they pass in either mode, then set these
// to __VARIANT__.
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index 87801a9658f6..1db222df0bc7 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -18,6 +18,7 @@ export const {
alwaysThrottleRetries,
disableLegacyContextForFunctionComponents,
disableSchedulerTimeoutInWorkLoop,
+ enableEffectEventMutationPhase,
enableHiddenSubtreeInsertionEffectCleanup,
enableInfiniteRenderLoopDetection,
enableNoCloningMemoCache,
@@ -49,7 +50,6 @@ export const enableUpdaterTracking = __PROFILE__;
export const enableSuspenseAvoidThisFallback: boolean = true;
export const enableCPUSuspense: boolean = true;
-export const enableUseEffectEventHook: boolean = true;
export const enableMoveBefore: boolean = false;
export const disableInputAttributeSyncing: boolean = false;
export const enableLegacyFBSupport: boolean = true;