@samitouri / QOS-React-2 / commits / 93d4458fdc

[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 });