Repository: facebook/react
Pull request: [Fiber] Ensure `useEffectEvent` reads latest values in `forwardRef` and `memo()` Components

Closes https://github.com/facebook/react/issues/34818

Seems to me the event payload handling should've gone into the original fallthrough block instead of just handling it for `FunctionComponent`: https://github.com/facebook/react/pull/25229/files#diff-9e8ee221dbf99e0ccdad6a63bf63804dea26f1785e2f443dbe92fa835a63b83eL412-L415

See first commit for current behavior where Effect Events did not read the latest Context value if used in `React.memo` Components.

Changed files:
filename: packages/react-reconciler/src/ReactFiberCommitWork.js
patch: @@ -496,7 +496,9 @@ function commitBeforeMutationEffectsOnFiber(
   }
 
   switch (finishedWork.tag) {
-    case FunctionComponent: {
+    case FunctionComponent:
+    case ForwardRef:
+    case SimpleMemoComponent: {
       if (enableUseEffectEventHook) {
         if ((flags & Update) !== NoFlags) {
           const updateQueue: FunctionComponentUpdateQueue | null =
@@ -513,10 +515,6 @@ function commitBeforeMutationEffectsOnFiber(
       }
       break;
     }
-    case ForwardRef:
-    case SimpleMemoComponent: {
-      break;
-    }
     case ClassComponent: {
       if ((flags & Snapshot) !== NoFlags) {
         if (current !== null) {

filename: packages/react-reconciler/src/__tests__/useEffectEvent-test.js
patch: @@ -850,4 +850,82 @@ describe('useEffectEvent', () => {
     );
     assertLog(['Add to cart', 'url: /shop/2, numberOfItems: 1']);
   });
+
+  it('reads the latest context value in memo Components', async () => {
+    const MyContext = createContext('default');
+
+    let logContextValue;
+    const ContextReader = React.memo(function ContextReader() {
+      const value = useContext(MyContext);
+      Scheduler.log('ContextReader: ' + value);
+      const fireLogContextValue = useEffectEvent(() => {
+        Scheduler.log('ContextReader (Effect event): ' + value);
+      });
+      useEffect(() => {
+        logContextValue = fireLogContextValue;
+      }, []);
+      return null;
+    });
+
+    function App({value}) {
+      return (
+        <MyContext.Provider value={value}>
+          <ContextReader />
+        </MyContext.Provider>
+      );
+    }
+
+    const root = ReactNoop.createRoot();
+    await act(() => root.render(<App value="first" />));
+    assertLog(['ContextReader: first']);
+
+    logContextValue();
+
+    assertLog(['ContextReader (Effect event): first']);
+
+    await act(() => root.render(<App value="second" />));
+    assertLog(['ContextReader: second']);
+
+    logContextValue();
+    assertLog(['ContextReader (Effect event): second']);
+  });
+
+  it('reads the latest context value in forwardRef Components', async () => {
+    const MyContext = createContext('default');
+
+    let logContextValue;
+    const ContextReader = React.forwardRef(function ContextReader(props, ref) {
+      const value = useContext(MyContext);
+      Scheduler.log('ContextReader: ' + value);
+      const fireLogContextValue = useEffectEvent(() => {
+        Scheduler.log('ContextReader (Effect event): ' + value);
+      });
+      useEffect(() => {
+        logContextValue = fireLogContextValue;
+      }, []);
+      return null;
+    });
+
+    function App({value}) {
+      return (
+        <MyContext.Provider value={value}>
+          <ContextReader />
+        </MyContext.Provider>
+      );
+    }
+
+    const root = ReactNoop.createRoot();
+    await act(() => root.render(<App value="first" />));
+    assertLog(['ContextReader: first']);
+
+    logContextValue();
+
+    assertLog(['ContextReader (Effect event): first']);
+
+    await act(() => root.render(<App value="second" />));
+    assertLog(['ContextReader: second']);
+
+    logContextValue();
+    assertLog(['ContextReader (Effect event): second']);
+  });
 });