[flags] add enableEffectEventMutationPhase (#35548)
Small optimization for useEffectEvent. Not sure we even need a flag for it, but it will be a nice killswitch. As an added benefit, it fixes a bug when `enableViewTransition` is on, where we were not updating the useEffectEvent callback when a tree went from hidden to visible.
Ricky committed
Feb 4, 2026 at 15:04 UTC
3aaab92a265ebeb43b15e7c30c2f1dfb9fcd5961
12 files changed
+458
-6
packages/react-reconciler/src/ReactFiberCommitWork.js
+20
-1
@@ -47,6 +47,7 @@ import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
47
import {
48
alwaysThrottleRetries,
49
enableCreateEventHandleAPI,
50
+ enableEffectEventMutationPhase,
51
enableHiddenSubtreeInsertionEffectCleanup,
52
enableProfilerTimer,
53
enableProfilerCommitHooks,
@@ -499,7 +500,7 @@ function commitBeforeMutationEffectsOnFiber(
500
case FunctionComponent:
501
case ForwardRef:
502
case SimpleMemoComponent: {
502
- if ((flags & Update) !== NoFlags) {
503
+ if (!enableEffectEventMutationPhase && (flags & Update) !== NoFlags) {
504
const updateQueue: FunctionComponentUpdateQueue | null =
505
(finishedWork.updateQueue: any);
506
const eventPayloads = updateQueue !== null ? updateQueue.events : null;
@@ -2042,6 +2043,24 @@ function commitMutationEffectsOnFiber(
2043
case ForwardRef:
2044
case MemoComponent:
2045
case SimpleMemoComponent: {
2046
+ // Mutate event effect callbacks on the way down, before mutation effects.
2047
+ // This ensures that parent event effects are mutated before child effects.
2048
+ // This isn't a supported use case, so we can re-consider it,
2049
+ // but this was the behavior we originally shipped.
2050
+ if (enableEffectEventMutationPhase) {
2051
+ if (flags & Update) {
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
+ }
2063
+ }
2064
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2065
commitReconciliationEffects(finishedWork, lanes);
2066
packages/react-reconciler/src/ReactFiberFlags.js
+9
-5
@@ -7,7 +7,10 @@
7
* @flow
8
*/
9
10
-import {enableCreateEventHandleAPI} from 'shared/ReactFeatureFlags';
10
+import {
11
+ enableCreateEventHandleAPI,
12
+ enableEffectEventMutationPhase,
13
+} from 'shared/ReactFeatureFlags';
14
15
export type Flags = number;
16
@@ -99,10 +102,11 @@ export const BeforeMutationMask: number =
102
// TODO: Only need to visit Deletions during BeforeMutation phase if an
103
// element is focused.
104
Update | ChildDeletion | Visibility
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);
105
+ : // useEffectEvent uses the snapshot phase,
106
+ // but we're moving it to the mutation phase.
107
+ enableEffectEventMutationPhase
108
+ ? 0
109
+ : Update);
110
111
// For View Transition support we use the snapshot phase to scan the tree for potentially
112
// affected ViewTransition components.
packages/react-reconciler/src/__tests__/useEffectEvent-test.js
+416
@@ -27,6 +27,7 @@ describe('useEffectEvent', () => {
27
let waitForAll;
28
let assertLog;
29
let waitForThrow;
30
+ let waitFor;
31
32
beforeEach(() => {
33
React = require('react');
@@ -46,6 +47,7 @@ describe('useEffectEvent', () => {
47
waitForAll = InternalTestUtils.waitForAll;
48
assertLog = InternalTestUtils.assertLog;
49
waitForThrow = InternalTestUtils.waitForThrow;
50
+ waitFor = InternalTestUtils.waitFor;
51
});
52
53
function Text(props) {
@@ -595,6 +597,358 @@ describe('useEffectEvent', () => {
597
assertLog(['Effect value: 2', 'Event value: 2']);
598
});
599
600
+ it('fires all (interleaved) effects with useEffectEvent in correct order', async () => {
601
+ function CounterA({count}) {
602
+ const onEvent = useEffectEvent(() => {
603
+ return `A ${count}`;
604
+ });
605
+
606
+ useInsertionEffect(() => {
607
+ // Call the event function to verify it sees the latest value
608
+ Scheduler.log(`Parent Insertion Create: ${onEvent()}`);
609
+ return () => {
610
+ Scheduler.log(`Parent Insertion Create: ${onEvent()}`);
611
+ };
612
+ });
613
+
614
+ useLayoutEffect(() => {
615
+ Scheduler.log(`Parent Layout Create: ${onEvent()}`);
616
+ return () => {
617
+ Scheduler.log(`Parent Layout Cleanup: ${onEvent()}`);
618
+ };
619
+ });
620
+
621
+ useEffect(() => {
622
+ Scheduler.log(`Parent Passive Create: ${onEvent()}`);
623
+ return () => {
624
+ Scheduler.log(`Parent Passive Destroy ${onEvent()}`);
625
+ };
626
+ });
627
+
628
+ // this breaks the rules, but ensures the ordering is correct.
629
+ return <CounterB count={count} onEventParent={onEvent} />;
630
+ }
631
+
632
+ function CounterB({count, onEventParent}) {
633
+ const onEvent = useEffectEvent(() => {
634
+ return `${onEventParent()} B ${count}`;
635
+ });
636
+
637
+ useInsertionEffect(() => {
638
+ Scheduler.log(`Child Insertion Create ${onEvent()}`);
639
+ return () => {
640
+ Scheduler.log(`Child Insertion Destroy ${onEvent()}`);
641
+ };
642
+ });
643
+
644
+ useLayoutEffect(() => {
645
+ Scheduler.log(`Child Layout Create ${onEvent()}`);
646
+ return () => {
647
+ Scheduler.log(`Child Layout Destroy ${onEvent()}`);
648
+ };
649
+ });
650
+
651
+ useEffect(() => {
652
+ Scheduler.log(`Child Passive Create ${onEvent()}`);
653
+ return () => {
654
+ Scheduler.log(`Child Passive Destroy ${onEvent()}`);
655
+ };
656
+ });
657
+
658
+ return null;
659
+ }
660
+
661
+ await act(async () => {
662
+ ReactNoop.render(<CounterA count={1} />);
663
+ });
664
+
665
+ assertLog([
666
+ 'Child Insertion Create A 1 B 1',
667
+ 'Parent Insertion Create: A 1',
668
+ 'Child Layout Create A 1 B 1',
669
+ 'Parent Layout Create: A 1',
670
+ 'Child Passive Create A 1 B 1',
671
+ 'Parent Passive Create: A 1',
672
+ ]);
673
+
674
+ await act(async () => {
675
+ ReactNoop.render(<CounterA count={2} />);
676
+ });
677
+
678
+ assertLog([
679
+ 'Child Insertion Destroy A 2 B 2',
680
+ 'Child Insertion Create A 2 B 2',
681
+ 'Child Layout Destroy A 2 B 2',
682
+ 'Parent Insertion Create: A 2',
683
+ 'Parent Insertion Create: A 2',
684
+ 'Parent Layout Cleanup: A 2',
685
+ 'Child Layout Create A 2 B 2',
686
+ 'Parent Layout Create: A 2',
687
+ 'Child Passive Destroy A 2 B 2',
688
+ 'Parent Passive Destroy A 2',
689
+ 'Child Passive Create A 2 B 2',
690
+ 'Parent Passive Create: A 2',
691
+ ]);
692
+
693
+ // Unmount everything
694
+ await act(async () => {
695
+ ReactNoop.render(null);
696
+ });
697
+
698
+ assertLog([
699
+ 'Parent Insertion Create: A 2',
700
+ 'Parent Layout Cleanup: A 2',
701
+ 'Child Insertion Destroy A 2 B 2',
702
+ 'Child Layout Destroy A 2 B 2',
703
+ 'Parent Passive Destroy A 2',
704
+ 'Child Passive Destroy A 2 B 2',
705
+ ]);
706
+ });
707
+
708
+ it('correctly mutates effect event with Activity', async () => {
709
+ let setState;
710
+ let setChildState;
711
+ function CounterA({count, hideChild}) {
712
+ const [state, _setState] = useState(1);
713
+ setState = _setState;
714
+ const onEvent = useEffectEvent(() => {
715
+ return `A ${count} ${state}`;
716
+ });
717
+
718
+ useInsertionEffect(() => {
719
+ // Call the event function to verify it sees the latest value
720
+ Scheduler.log(`Parent Insertion Create: ${onEvent()}`);
721
+ return () => {
722
+ Scheduler.log(`Parent Insertion Create: ${onEvent()}`);
723
+ };
724
+ });
725
+
726
+ useLayoutEffect(() => {
727
+ Scheduler.log(`Parent Layout Create: ${onEvent()}`);
728
+ return () => {
729
+ Scheduler.log(`Parent Layout Cleanup: ${onEvent()}`);
730
+ };
731
+ });
732
+
733
+ // this breaks the rules, but ensures the ordering is correct.
734
+ return (
735
+ <React.Activity mode={hideChild ? 'hidden' : 'visible'}>
736
+ <CounterB count={count} state={state} onEventParent={onEvent} />
737
+ </React.Activity>
738
+ );
739
+ }
740
+
741
+ function CounterB({count, state, onEventParent}) {
742
+ const [childState, _setChildState] = useState(1);
743
+ setChildState = _setChildState;
744
+ const onEvent = useEffectEvent(() => {
745
+ return `${onEventParent()} B ${count} ${state} ${childState}`;
746
+ });
747
+
748
+ useInsertionEffect(() => {
749
+ Scheduler.log(`Child Insertion Create ${onEvent()}`);
750
+ return () => {
751
+ Scheduler.log(`Child Insertion Destroy ${onEvent()}`);
752
+ };
753
+ });
754
+
755
+ useLayoutEffect(() => {
756
+ Scheduler.log(`Child Layout Create ${onEvent()}`);
757
+ return () => {
758
+ Scheduler.log(`Child Layout Destroy ${onEvent()}`);
759
+ };
760
+ });
761
+
762
+ useEffect(() => {
763
+ Scheduler.log(`Child Passive Create ${onEvent()}`);
764
+ return () => {
765
+ Scheduler.log(`Child Passive Destroy ${onEvent()}`);
766
+ };
767
+ });
768
+
769
+ return null;
770
+ }
771
+
772
+ await act(async () => {
773
+ ReactNoop.render(<CounterA count={1} hideChild={true} />);
774
+ await waitFor([
775
+ 'Parent Insertion Create: A 1 1',
776
+ 'Parent Layout Create: A 1 1',
777
+ 'Child Insertion Create A 1 1 B 1 1 1',
778
+ ]);
779
+ });
780
+
781
+ assertLog([]);
782
+
783
+ await act(async () => {
784
+ ReactNoop.render(<CounterA count={2} hideChild={true} />);
785
+
786
+ await waitFor([
787
+ 'Parent Insertion Create: A 2 1',
788
+ 'Parent Insertion Create: A 2 1',
789
+ 'Parent Layout Cleanup: A 2 1',
790
+ 'Parent Layout Create: A 2 1',
791
+ ...(gate('enableViewTransition') &&
792
+ !gate('enableEffectEventMutationPhase')
793
+ ? [
794
+ 'Child Insertion Destroy A 2 1 B 1 1 1',
795
+ 'Child Insertion Create A 2 1 B 1 1 1',
796
+ ]
797
+ : [
798
+ 'Child Insertion Destroy A 2 1 B 2 1 1',
799
+ 'Child Insertion Create A 2 1 B 2 1 1',
800
+ ]),
801
+ ]);
802
+ });
803
+
804
+ assertLog([]);
805
+
806
+ await act(async () => {
807
+ setState(2);
808
+
809
+ await waitFor([
810
+ 'Parent Insertion Create: A 2 2',
811
+ 'Parent Insertion Create: A 2 2',
812
+ 'Parent Layout Cleanup: A 2 2',
813
+ 'Parent Layout Create: A 2 2',
814
+ ...(gate('enableViewTransition') &&
815
+ !gate('enableEffectEventMutationPhase')
816
+ ? [
817
+ 'Child Insertion Destroy A 2 2 B 1 1 1',
818
+ 'Child Insertion Create A 2 2 B 1 1 1',
819
+ ]
820
+ : [
821
+ 'Child Insertion Destroy A 2 2 B 2 2 1',
822
+ 'Child Insertion Create A 2 2 B 2 2 1',
823
+ ]),
824
+ ]);
825
+ });
826
+
827
+ assertLog([]);
828
+
829
+ await act(async () => {
830
+ setChildState(2);
831
+
832
+ await waitFor(
833
+ gate('enableViewTransition') && !gate('enableEffectEventMutationPhase')
834
+ ? [
835
+ 'Child Insertion Destroy A 2 2 B 1 1 1',
836
+ 'Child Insertion Create A 2 2 B 1 1 1',
837
+ ]
838
+ : [
839
+ 'Child Insertion Destroy A 2 2 B 2 2 2',
840
+ 'Child Insertion Create A 2 2 B 2 2 2',
841
+ ],
842
+ );
843
+ });
844
+
845
+ assertLog([]);
846
+
847
+ await act(async () => {
848
+ ReactNoop.render(<CounterA count={3} hideChild={true} />);
849
+
850
+ await waitFor([
851
+ 'Parent Insertion Create: A 3 2',
852
+ 'Parent Insertion Create: A 3 2',
853
+ 'Parent Layout Cleanup: A 3 2',
854
+ 'Parent Layout Create: A 3 2',
855
+ ]);
856
+ });
857
+
858
+ assertLog(
859
+ gate('enableViewTransition') && !gate('enableEffectEventMutationPhase')
860
+ ? [
861
+ 'Child Insertion Destroy A 3 2 B 1 1 1',
862
+ 'Child Insertion Create A 3 2 B 1 1 1',
863
+ ]
864
+ : [
865
+ 'Child Insertion Destroy A 3 2 B 3 2 2',
866
+ 'Child Insertion Create A 3 2 B 3 2 2',
867
+ ],
868
+ );
869
+
870
+ await act(async () => {
871
+ ReactNoop.render(<CounterA count={3} hideChild={false} />);
872
+
873
+ await waitFor([
874
+ ...(gate('enableViewTransition') &&
875
+ !gate('enableEffectEventMutationPhase')
876
+ ? [
877
+ 'Child Insertion Destroy A 3 2 B 1 1 1',
878
+ 'Child Insertion Create A 3 2 B 1 1 1',
879
+ ]
880
+ : [
881
+ 'Child Insertion Destroy A 3 2 B 3 2 2',
882
+ 'Child Insertion Create A 3 2 B 3 2 2',
883
+ ]),
884
+ 'Parent Insertion Create: A 3 2',
885
+ 'Parent Insertion Create: A 3 2',
886
+ 'Parent Layout Cleanup: A 3 2',
887
+ ...(gate('enableViewTransition') &&
888
+ !gate('enableEffectEventMutationPhase')
889
+ ? ['Child Layout Create A 3 2 B 1 1 1']
890
+ : ['Child Layout Create A 3 2 B 3 2 2']),
891
+
892
+ 'Parent Layout Create: A 3 2',
893
+ ]);
894
+ });
895
+
896
+ assertLog(
897
+ gate('enableViewTransition') && !gate('enableEffectEventMutationPhase')
898
+ ? ['Child Passive Create A 3 2 B 1 1 1']
899
+ : ['Child Passive Create A 3 2 B 3 2 2'],
900
+ );
901
+
902
+ await act(async () => {
903
+ ReactNoop.render(<CounterA count={3} hideChild={true} />);
904
+
905
+ await waitFor([
906
+ ...(gate('enableViewTransition') &&
907
+ !gate('enableEffectEventMutationPhase')
908
+ ? ['Child Layout Destroy A 3 2 B 1 1 1']
909
+ : ['Child Layout Destroy A 3 2 B 3 2 2']),
910
+ 'Parent Insertion Create: A 3 2',
911
+ 'Parent Insertion Create: A 3 2',
912
+ 'Parent Layout Cleanup: A 3 2',
913
+ 'Parent Layout Create: A 3 2',
914
+ ...(gate('enableViewTransition') &&
915
+ !gate('enableEffectEventMutationPhase')
916
+ ? ['Child Passive Destroy A 3 2 B 1 1 1']
917
+ : ['Child Passive Destroy A 3 2 B 3 2 2']),
918
+ ]);
919
+ });
920
+
921
+ assertLog(
922
+ gate('enableViewTransition') && !gate('enableEffectEventMutationPhase')
923
+ ? [
924
+ 'Child Insertion Destroy A 3 2 B 1 1 1',
925
+ 'Child Insertion Create A 3 2 B 1 1 1',
926
+ ]
927
+ : [
928
+ 'Child Insertion Destroy A 3 2 B 3 2 2',
929
+ 'Child Insertion Create A 3 2 B 3 2 2',
930
+ ],
931
+ );
932
+
933
+ // Unmount everything
934
+ await act(async () => {
935
+ ReactNoop.render(null);
936
+ });
937
+
938
+ assertLog([
939
+ 'Parent Insertion Create: A 3 2',
940
+ 'Parent Layout Cleanup: A 3 2',
941
+ ...(gate('enableHiddenSubtreeInsertionEffectCleanup')
942
+ ? [
943
+ gate('enableViewTransition') &&
944
+ !gate('enableEffectEventMutationPhase')
945
+ ? 'Child Insertion Destroy A 3 2 B 1 1 1'
946
+ : 'Child Insertion Destroy A 3 2 B 3 2 2',
947
+ ]
948
+ : []),
949
+ ]);
950
+ });
951
+
952
it("doesn't provide a stable identity", async () => {
953
function Counter({shouldRender, value}) {
954
const onClick = useEffectEvent(() => {
@@ -916,4 +1270,66 @@ describe('useEffectEvent', () => {
1270
logContextValue();
1271
assertLog(['ContextReader (Effect event): second']);
1272
});
1273
+
1274
+ // @gate enableActivity
1275
+ it('effect events are fresh inside Activity', async () => {
1276
+ function Child({value}) {
1277
+ const getValue = useEffectEvent(() => {
1278
+ return value;
1279
+ });
1280
+ useInsertionEffect(() => {
1281
+ Scheduler.log('insertion create: ' + getValue());
1282
+ return () => {
1283
+ Scheduler.log('insertion destroy: ' + getValue());
1284
+ };
1285
+ });
1286
+ useLayoutEffect(() => {
1287
+ Scheduler.log('layout create: ' + getValue());
1288
+ return () => {
1289
+ Scheduler.log('layout destroy: ' + getValue());
1290
+ };
1291
+ });
1292
+
1293
+ Scheduler.log('render: ' + value);
1294
+ return null;
1295
+ }
1296
+
1297
+ function App({value, mode}) {
1298
+ return (
1299
+ <React.Activity mode={mode}>
1300
+ <Child value={value} />
1301
+ </React.Activity>
1302
+ );
1303
+ }
1304
+
1305
+ const root = ReactNoop.createRoot();
1306
+
1307
+ // Mount hidden
1308
+ await act(async () => root.render(<App value={1} mode="hidden" />));
1309
+ assertLog(['render: 1', 'insertion create: 1']);
1310
+
1311
+ // Update, still hidden
1312
+ await act(async () => root.render(<App value={2} mode="hidden" />));
1313
+
1314
+ // Bug in enableViewTransition. Insertion and layout see stale closure.
1315
+ assertLog([
1316
+ 'render: 2',
1317
+ ...(gate('enableViewTransition') &&
1318
+ !gate('enableEffectEventMutationPhase')
1319
+ ? ['insertion destroy: 1', 'insertion create: 1']
1320
+ : ['insertion destroy: 2', 'insertion create: 2']),
1321
+ ]);
1322
+
1323
+ // Switch to visible
1324
+ await act(async () => root.render(<App value={2} mode="visible" />));
1325
+
1326
+ // Bug in enableViewTransition. Even when switching to visible, sees stale closure.
1327
+ assertLog([
1328
+ 'render: 2',
1329
+ ...(gate('enableViewTransition') &&
1330
+ !gate('enableEffectEventMutationPhase')
1331
+ ? ['insertion destroy: 1', 'insertion create: 1', 'layout create: 1']
1332
+ : ['insertion destroy: 2', 'insertion create: 2', 'layout create: 2']),
1333
+ ]);
1334
+ });
1335
});
packages/shared/ReactFeatureFlags.js
+4
@@ -123,6 +123,10 @@ export const enableFizzExternalRuntime = __EXPERIMENTAL__;
123
124
export const alwaysThrottleRetries: boolean = true;
125
126
+// Gate whether useEffectEvent uses the mutation phase (true) or before-mutation
127
+// phase (false) for updating event function references.
128
+export const enableEffectEventMutationPhase: boolean = false;
129
+
130
export const passChildrenWhenCloningPersistedNodes: boolean = false;
131
132
export const enableEagerAlternateStateNodeCleanup: boolean = true;
packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
+1
@@ -25,4 +25,5 @@ export const passChildrenWhenCloningPersistedNodes = __VARIANT__;
25
export const enableFragmentRefs = __VARIANT__;
26
export const enableFragmentRefsScrollIntoView = __VARIANT__;
27
export const enableFragmentRefsInstanceHandles = __VARIANT__;
28
+export const enableEffectEventMutationPhase = __VARIANT__;
29
export const enableFragmentRefsTextNodes = __VARIANT__;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -20,6 +20,7 @@ const dynamicFlags: DynamicExportsType = (dynamicFlagsUntyped: any);
20
// the exports object every time a flag is read.
21
export const {
22
alwaysThrottleRetries,
23
+ enableEffectEventMutationPhase,
24
enableHiddenSubtreeInsertionEffectCleanup,
25
enableObjectFiber,
26
enableEagerAlternateStateNodeCleanup,
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -46,6 +46,7 @@ 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;
50
export const enableSuspenseAvoidThisFallback: boolean = false;
51
export const enableSuspenseCallback: boolean = false;
52
export const enableTaint: boolean = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -56,6 +56,7 @@ export const disableClientCache: boolean = true;
56
export const enableInfiniteRenderLoopDetection: boolean = false;
57
58
export const enableEagerAlternateStateNodeCleanup: boolean = true;
59
+export const enableEffectEventMutationPhase: boolean = false;
60
61
export const enableYieldingBeforePassive: boolean = true;
62
packages/shared/forks/ReactFeatureFlags.test-renderer.native-fb.js
+1
@@ -43,6 +43,7 @@ export const enableComponentPerformanceTrack = false;
43
export const enablePerformanceIssueReporting = false;
44
export const enableScopeAPI = false;
45
export const enableEagerAlternateStateNodeCleanup = true;
46
+export const enableEffectEventMutationPhase = false;
47
export const enableSuspenseAvoidThisFallback = false;
48
export const enableSuspenseCallback = false;
49
export const enableTaint = true;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -62,6 +62,7 @@ export const disableLegacyMode: boolean = true;
62
63
export const enableObjectFiber: boolean = false;
64
export const enableEagerAlternateStateNodeCleanup: boolean = true;
65
+export const enableEffectEventMutationPhase: boolean = false;
66
67
export const enableYieldingBeforePassive: boolean = false;
68
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+2
@@ -39,6 +39,8 @@ export const enableInternalInstanceMap: boolean = __VARIANT__;
39
export const enableTrustedTypesIntegration: boolean = __VARIANT__;
40
export const enableParallelTransitions: boolean = __VARIANT__;
41
42
+export const enableEffectEventMutationPhase: boolean = __VARIANT__;
43
+
44
// TODO: These flags are hard-coded to the default values used in open source.
45
// Update the tests so that they pass in either mode, then set these
46
// to __VARIANT__.
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -18,6 +18,7 @@ export const {
18
alwaysThrottleRetries,
19
disableLegacyContextForFunctionComponents,
20
disableSchedulerTimeoutInWorkLoop,
21
+ enableEffectEventMutationPhase,
22
enableHiddenSubtreeInsertionEffectCleanup,
23
enableInfiniteRenderLoopDetection,
24
enableNoCloningMemoCache,