[Fiber] Ensure `useEffectEvent` reads latest values in `forwardRef` and `memo()` Components (#34831)
Sebastian "Sebbie" Silbermann committed
Oct 13, 2025 at 17:58 UTC
93d4458fdc054929e54fb25017d237ed85415533
2 files changed
+81
-5
packages/react-reconciler/src/ReactFiberCommitWork.js
+3
-5
@@ -496,7 +496,9 @@ function commitBeforeMutationEffectsOnFiber(
496
}
497
498
switch (finishedWork.tag) {
499
- case FunctionComponent: {
499
+ case FunctionComponent:
500
+ case ForwardRef:
501
+ case SimpleMemoComponent: {
502
if (enableUseEffectEventHook) {
503
if ((flags & Update) !== NoFlags) {
504
const updateQueue: FunctionComponentUpdateQueue | null =
@@ -513,10 +515,6 @@ function commitBeforeMutationEffectsOnFiber(
515
}
516
break;
517
}
516
- case ForwardRef:
517
- case SimpleMemoComponent: {
518
- break;
519
- }
518
case ClassComponent: {
519
if ((flags & Snapshot) !== NoFlags) {
520
if (current !== null) {
packages/react-reconciler/src/__tests__/useEffectEvent-test.js
+78
@@ -850,4 +850,82 @@ describe('useEffectEvent', () => {
850
);
851
assertLog(['Add to cart', 'url: /shop/2, numberOfItems: 1']);
852
});
853
+
854
+ it('reads the latest context value in memo Components', async () => {
855
+ const MyContext = createContext('default');
856
+
857
+ let logContextValue;
858
+ const ContextReader = React.memo(function ContextReader() {
859
+ const value = useContext(MyContext);
860
+ Scheduler.log('ContextReader: ' + value);
861
+ const fireLogContextValue = useEffectEvent(() => {
862
+ Scheduler.log('ContextReader (Effect event): ' + value);
863
+ });
864
+ useEffect(() => {
865
+ logContextValue = fireLogContextValue;
866
+ }, []);
867
+ return null;
868
+ });
869
+
870
+ function App({value}) {
871
+ return (
872
+ <MyContext.Provider value={value}>
873
+ <ContextReader />
874
+ </MyContext.Provider>
875
+ );
876
+ }
877
+
878
+ const root = ReactNoop.createRoot();
879
+ await act(() => root.render(<App value="first" />));
880
+ assertLog(['ContextReader: first']);
881
+
882
+ logContextValue();
883
+
884
+ assertLog(['ContextReader (Effect event): first']);
885
+
886
+ await act(() => root.render(<App value="second" />));
887
+ assertLog(['ContextReader: second']);
888
+
889
+ logContextValue();
890
+ assertLog(['ContextReader (Effect event): second']);
891
+ });
892
+
893
+ it('reads the latest context value in forwardRef Components', async () => {
894
+ const MyContext = createContext('default');
895
+
896
+ let logContextValue;
897
+ const ContextReader = React.forwardRef(function ContextReader(props, ref) {
898
+ const value = useContext(MyContext);
899
+ Scheduler.log('ContextReader: ' + value);
900
+ const fireLogContextValue = useEffectEvent(() => {
901
+ Scheduler.log('ContextReader (Effect event): ' + value);
902
+ });
903
+ useEffect(() => {
904
+ logContextValue = fireLogContextValue;
905
+ }, []);
906
+ return null;
907
+ });
908
+
909
+ function App({value}) {
910
+ return (
911
+ <MyContext.Provider value={value}>
912
+ <ContextReader />
913
+ </MyContext.Provider>
914
+ );
915
+ }
916
+
917
+ const root = ReactNoop.createRoot();
918
+ await act(() => root.render(<App value="first" />));
919
+ assertLog(['ContextReader: first']);
920
+
921
+ logContextValue();
922
+
923
+ assertLog(['ContextReader (Effect event): first']);
924
+
925
+ await act(() => root.render(<App value="second" />));
926
+ assertLog(['ContextReader: second']);
927
+
928
+ logContextValue();
929
+ assertLog(['ContextReader (Effect event): second']);
930
+ });
931
});