[internal] revert change merged accidentally (#35546)
I accidentally pushed this to new flag to https://github.com/facebook/react/pull/35541 and then merged it. Reverting it so I can submit a review.
Ricky committed
Jan 17, 2026 at 13:21 UTC
be3fb29904a23322bd3cef7752df517fabdc188a
12 files changed
+6
-245
packages/react-reconciler/src/ReactFiberCommitWork.js
+1
-15
@@ -47,7 +47,6 @@ import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
47
import {
48
alwaysThrottleRetries,
49
enableCreateEventHandleAPI,
50
- enableEffectEventMutationPhase,
50
enableHiddenSubtreeInsertionEffectCleanup,
51
enableProfilerTimer,
52
enableProfilerCommitHooks,
@@ -500,7 +499,7 @@ function commitBeforeMutationEffectsOnFiber(
499
case FunctionComponent:
500
case ForwardRef:
501
case SimpleMemoComponent: {
503
- if (!enableEffectEventMutationPhase && (flags & Update) !== NoFlags) {
502
+ if ((flags & Update) !== NoFlags) {
503
const updateQueue: FunctionComponentUpdateQueue | null =
504
(finishedWork.updateQueue: any);
505
const eventPayloads = updateQueue !== null ? updateQueue.events : null;
@@ -2047,19 +2046,6 @@ function commitMutationEffectsOnFiber(
2046
commitReconciliationEffects(finishedWork, lanes);
2047
2048
if (flags & Update) {
2050
- // Mutate event effect callbacks before insertion effects.
2051
- if (enableEffectEventMutationPhase) {
2052
- const updateQueue: FunctionComponentUpdateQueue | null =
2053
- (finishedWork.updateQueue: any);
2054
- const eventPayloads =
2055
- updateQueue !== null ? updateQueue.events : null;
2056
- if (eventPayloads !== null) {
2057
- for (let ii = 0; ii < eventPayloads.length; ii++) {
2058
- const {ref, nextImpl} = eventPayloads[ii];
2059
- ref.impl = nextImpl;
2060
- }
2061
- }
2062
- }
2049
commitHookEffectListUnmount(
2050
HookInsertion | HookHasEffect,
2051
finishedWork,
packages/react-reconciler/src/ReactFiberFlags.js
+5
-9
@@ -7,10 +7,7 @@
7
* @flow
8
*/
9
10
-import {
11
- enableCreateEventHandleAPI,
12
- enableEffectEventMutationPhase,
13
-} from 'shared/ReactFeatureFlags';
10
+import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
11
12
export type Flags = number;
13
@@ -102,11 +99,10 @@ export const BeforeMutationMask: number =
99
// TODO: Only need to visit Deletions during BeforeMutation phase if an
100
// element is focused.
101
Update | ChildDeletion | Visibility
105
- : // useEffectEvent uses the snapshot phase,
106
- // but we're moving it to the mutation phase.
107
- enableEffectEventMutationPhase
108
- ? 0
109
- : Update);
102
+ : // TODO: The useEffectEvent hook uses the snapshot phase for clean up but it
103
+ // really should use the mutation phase for this or at least schedule an
104
+ // explicit Snapshot phase flag for this.
105
+ Update);
106
107
// For View Transition support we use the snapshot phase to scan the tree for potentially
108
// affected ViewTransition components.
packages/react-reconciler/src/__tests__/useEffectEvent-test.js
-208
@@ -595,214 +595,6 @@ describe('useEffectEvent', () => {
595
assertLog(['Effect value: 2', 'Event value: 2']);
596
});
597
598
- it('updates parent and child event effects before their respective effect lifecycles', async () => {
599
- function Parent({value}) {
600
- const parentEvent = useEffectEvent(() => {
601
- Scheduler.log('Parent event: ' + value);
602
- });
603
-
604
- useInsertionEffect(() => {
605
- Scheduler.log('Parent insertion');
606
- parentEvent();
607
- }, [value]);
608
-
609
- return <Child value={value} />;
610
- }
611
-
612
- function Child({value}) {
613
- const childEvent = useEffectEvent(() => {
614
- Scheduler.log('Child event: ' + value);
615
- });
616
-
617
- useInsertionEffect(() => {
618
- Scheduler.log('Child insertion');
619
- childEvent();
620
- }, [value]);
621
-
622
- return null;
623
- }
624
-
625
- ReactNoop.render(<Parent value={1} />);
626
- await waitForAll([
627
- 'Child insertion',
628
- 'Child event: 1',
629
- 'Parent insertion',
630
- 'Parent event: 1',
631
- ]);
632
-
633
- await act(() => ReactNoop.render(<Parent value={2} />));
634
- // Each component's event is updated before its own insertion effect runs
635
- assertLog([
636
- 'Child insertion',
637
- 'Child event: 2',
638
- 'Parent insertion',
639
- 'Parent event: 2',
640
- ]);
641
- });
642
-
643
- it('fires all insertion effects (interleaved) with useEffectEvent before firing any layout effects', async () => {
644
- // This test mirrors the 'fires all insertion effects (interleaved) before firing any layout effects'
645
- // test in ReactHooksWithNoopRenderer-test.js, but adds useEffectEvent to verify that
646
- // event payloads are updated before each component's insertion effects run.
647
- // It also includes passive effects to verify the full effect lifecycle.
648
- let committedA = '(empty)';
649
- let committedB = '(empty)';
650
-
651
- function CounterA(props) {
652
- const onEvent = useEffectEvent(() => {
653
- return `Event A [A: ${committedA}, B: ${committedB}]`;
654
- });
655
-
656
- useInsertionEffect(() => {
657
- // Call the event function to verify it sees the latest value
658
- Scheduler.log(
659
- `Create Insertion A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
660
- );
661
- committedA = String(props.count);
662
- return () => {
663
- Scheduler.log(
664
- `Destroy Insertion A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
665
- );
666
- };
667
- });
668
-
669
- useLayoutEffect(() => {
670
- Scheduler.log(
671
- `Create Layout A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
672
- );
673
- return () => {
674
- Scheduler.log(
675
- `Destroy Layout A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
676
- );
677
- };
678
- });
679
-
680
- useEffect(() => {
681
- Scheduler.log(
682
- `Create Passive A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
683
- );
684
- return () => {
685
- Scheduler.log(
686
- `Destroy Passive A [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
687
- );
688
- };
689
- });
690
-
691
- return null;
692
- }
693
-
694
- function CounterB(props) {
695
- const onEvent = useEffectEvent(() => {
696
- return `Event B [A: ${committedA}, B: ${committedB}]`;
697
- });
698
-
699
- useInsertionEffect(() => {
700
- // Call the event function to verify it sees the latest value
701
- Scheduler.log(
702
- `Create Insertion B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
703
- );
704
- committedB = String(props.count);
705
- return () => {
706
- Scheduler.log(
707
- `Destroy Insertion B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
708
- );
709
- };
710
- });
711
-
712
- useLayoutEffect(() => {
713
- Scheduler.log(
714
- `Create Layout B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
715
- );
716
- return () => {
717
- Scheduler.log(
718
- `Destroy Layout B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
719
- );
720
- };
721
- });
722
-
723
- useEffect(() => {
724
- Scheduler.log(
725
- `Create Passive B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
726
- );
727
- return () => {
728
- Scheduler.log(
729
- `Destroy Passive B [A: ${committedA}, B: ${committedB}], event: ${onEvent()}`,
730
- );
731
- };
732
- });
733
-
734
- return null;
735
- }
736
-
737
- await act(async () => {
738
- ReactNoop.render(
739
- <React.Fragment>
740
- <CounterA count={0} />
741
- <CounterB count={0} />
742
- </React.Fragment>,
743
- );
744
- // All insertion effects fire before all layout effects, then passive effects
745
- // Event functions should see the state AT THE TIME they're called
746
- await waitForAll([
747
- // Insertion effects (mutation phase)
748
- 'Create Insertion A [A: (empty), B: (empty)], event: Event A [A: (empty), B: (empty)]',
749
- 'Create Insertion B [A: 0, B: (empty)], event: Event B [A: 0, B: (empty)]',
750
- // Layout effects
751
- 'Create Layout A [A: 0, B: 0], event: Event A [A: 0, B: 0]',
752
- 'Create Layout B [A: 0, B: 0], event: Event B [A: 0, B: 0]',
753
- // Passive effects
754
- 'Create Passive A [A: 0, B: 0], event: Event A [A: 0, B: 0]',
755
- 'Create Passive B [A: 0, B: 0], event: Event B [A: 0, B: 0]',
756
- ]);
757
- expect([committedA, committedB]).toEqual(['0', '0']);
758
- });
759
-
760
- await act(async () => {
761
- ReactNoop.render(
762
- <React.Fragment>
763
- <CounterA count={1} />
764
- <CounterB count={1} />
765
- </React.Fragment>,
766
- );
767
- await waitForAll([
768
- // Component A: insertion destroy, then create
769
- 'Destroy Insertion A [A: 0, B: 0], event: Event A [A: 0, B: 0]',
770
- 'Create Insertion A [A: 0, B: 0], event: Event A [A: 0, B: 0]',
771
- // Component A: layout destroy (after insertion updated committedA)
772
- 'Destroy Layout A [A: 1, B: 0], event: Event A [A: 1, B: 0]',
773
- // Component B: insertion destroy, then create
774
- 'Destroy Insertion B [A: 1, B: 0], event: Event B [A: 1, B: 0]',
775
- 'Create Insertion B [A: 1, B: 0], event: Event B [A: 1, B: 0]',
776
- // Component B: layout destroy (after insertion updated committedB)
777
- 'Destroy Layout B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
778
- // Layout creates
779
- 'Create Layout A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
780
- 'Create Layout B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
781
- // Passive destroys then creates
782
- 'Destroy Passive A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
783
- 'Destroy Passive B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
784
- 'Create Passive A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
785
- 'Create Passive B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
786
- ]);
787
- expect([committedA, committedB]).toEqual(['1', '1']);
788
- });
789
-
790
- // Unmount everything
791
- await act(async () => {
792
- ReactNoop.render(null);
793
- await waitForAll([
794
- // Insertion and layout destroys (mutation/layout phase)
795
- 'Destroy Insertion A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
796
- 'Destroy Layout A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
797
- 'Destroy Insertion B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
798
- 'Destroy Layout B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
799
- // Passive destroys
800
- 'Destroy Passive A [A: 1, B: 1], event: Event A [A: 1, B: 1]',
801
- 'Destroy Passive B [A: 1, B: 1], event: Event B [A: 1, B: 1]',
802
- ]);
803
- });
804
- });
805
-
598
it("doesn't provide a stable identity", async () => {
599
function Counter({shouldRender, value}) {
600
const onClick = useEffectEvent(() => {
packages/shared/ReactFeatureFlags.js
-4
@@ -125,10 +125,6 @@ export const enableFizzExternalRuntime = __EXPERIMENTAL__;
125
126
export const alwaysThrottleRetries: boolean = true;
127
128
-// Gate whether useEffectEvent uses the mutation phase (true) or before-mutation
129
-// phase (false) for updating event function references.
130
-export const enableEffectEventMutationPhase: boolean = false;
131
-
128
export const passChildrenWhenCloningPersistedNodes: boolean = false;
129
130
export const enableEagerAlternateStateNodeCleanup: boolean = true;
packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
-1
@@ -27,4 +27,3 @@ export const enableFragmentRefs = __VARIANT__;
27
export const enableFragmentRefsScrollIntoView = __VARIANT__;
28
export const enableFragmentRefsInstanceHandles = __VARIANT__;
29
export const enableComponentPerformanceTrack = __VARIANT__;
30
-export const enableEffectEventMutationPhase = __VARIANT__;
packages/shared/forks/ReactFeatureFlags.native-fb.js
-1
@@ -20,7 +20,6 @@ const dynamicFlags: DynamicExportsType = (dynamicFlagsUntyped: any);
20
// the exports object every time a flag is read.
21
export const {
22
alwaysThrottleRetries,
23
- enableEffectEventMutationPhase,
23
enableHiddenSubtreeInsertionEffectCleanup,
24
enableObjectFiber,
25
enableEagerAlternateStateNodeCleanup,
packages/shared/forks/ReactFeatureFlags.native-oss.js
-1
@@ -46,7 +46,6 @@ export const enableSchedulingProfiler: boolean =
46
!enableComponentPerformanceTrack && __PROFILE__;
47
export const enableScopeAPI: boolean = false;
48
export const enableEagerAlternateStateNodeCleanup: boolean = true;
49
-export const enableEffectEventMutationPhase: boolean = false;
49
export const enableSuspenseAvoidThisFallback: boolean = false;
50
export const enableSuspenseCallback: boolean = false;
51
export const enableTaint: boolean = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
-1
@@ -58,7 +58,6 @@ export const enableInfiniteRenderLoopDetection: boolean = false;
58
59
export const renameElementSymbol: boolean = true;
60
export const enableEagerAlternateStateNodeCleanup: boolean = true;
61
-export const enableEffectEventMutationPhase: boolean = false;
61
62
export const enableYieldingBeforePassive: boolean = true;
63
packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
-1
@@ -43,7 +43,6 @@ export const enableComponentPerformanceTrack = false;
43
export const enablePerformanceIssueReporting = false;
44
export const enableScopeAPI = false;
45
export const enableEagerAlternateStateNodeCleanup = true;
46
-export const enableEffectEventMutationPhase = false;
46
export const enableSuspenseAvoidThisFallback = false;
47
export const enableSuspenseCallback = false;
48
export const enableTaint = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
-1
@@ -64,7 +64,6 @@ export const renameElementSymbol: boolean = false;
64
65
export const enableObjectFiber: boolean = false;
66
export const enableEagerAlternateStateNodeCleanup: boolean = true;
67
-export const enableEffectEventMutationPhase: boolean = false;
67
68
export const enableHydrationLaneScheduling: boolean = true;
69
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
-2
@@ -40,8 +40,6 @@ export const enableAsyncDebugInfo: boolean = __VARIANT__;
40
41
export const enableInternalInstanceMap: boolean = __VARIANT__;
42
43
-export const enableEffectEventMutationPhase: boolean = __VARIANT__;
44
-
43
// TODO: These flags are hard-coded to the default values used in open source.
44
// Update the tests so that they pass in either mode, then set these
45
// to __VARIANT__.
packages/shared/forks/ReactFeatureFlags.www.js
-1
@@ -18,7 +18,6 @@ export const {
18
alwaysThrottleRetries,
19
disableLegacyContextForFunctionComponents,
20
disableSchedulerTimeoutInWorkLoop,
21
- enableEffectEventMutationPhase,
21
enableHiddenSubtreeInsertionEffectCleanup,
22
enableInfiniteRenderLoopDetection,
23
enableNoCloningMemoCache,