Repository: facebook/react
Pull request: [lint] Allow useEffectEvent in useLayoutEffect and useInsertionEffect

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34492).
* #34497
* __->__ #34492

Changed files:
filename: packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
patch: @@ -1430,6 +1430,72 @@ if (__EXPERIMENTAL__) {
         }
       `,
     },
+    {
+      code: normalizeIndent`
+        // Valid because functions created with useEffectEvent can be called in useLayoutEffect.
+        function MyComponent({ theme }) {
+          const onClick = useEffectEvent(() => {
+            showNotification(theme);
+          });
+          useLayoutEffect(() => {
+            onClick();
+          });
+          React.useLayoutEffect(() => {
+            onClick();
+          });
+        }
+      `,
+    },
+    {
+      code: normalizeIndent`
+        // Valid because functions created with useEffectEvent can be called in useInsertionEffect.
+        function MyComponent({ theme }) {
+          const onClick = useEffectEvent(() => {
+            showNotification(theme);
+          });
+          useInsertionEffect(() => {
+            onClick();
+          });
+          React.useInsertionEffect(() => {
+            onClick();
+          });
+        }
+      `,
+    },
+    {
+      code: normalizeIndent`
+        // Valid because functions created with useEffectEvent can be passed by reference in useLayoutEffect 
+        // and useInsertionEffect.
+        function MyComponent({ theme }) {
+          const onClick = useEffectEvent(() => {
+            showNotification(theme);
+          });
+          const onClick2 = useEffectEvent(() => {
+            debounce(onClick);
+            debounce(() => onClick());
+            debounce(() => { onClick() });
+            deboucne(() => debounce(onClick));
+          });
+          useLayoutEffect(() => {
+            let id = setInterval(() => onClick(), 100);
+            return () => clearInterval(onClick);
+          }, []);
+          React.useLayoutEffect(() => {
+            let id = setInterval(() => onClick(), 100);
+            return () => clearInterval(onClick);
+          }, []);
+          useInsertionEffect(() => {
+            let id = setInterval(() => onClick(), 100);
+            return () => clearInterval(onClick);
+          }, []);
+          React.useInsertionEffect(() => {
+            let id = setInterval(() => onClick(), 100);
+            return () => clearInterval(onClick);
+          }, []);
+          return null;
+        }
+      `,
+    },
   ];
   allTests.invalid = [
     ...allTests.invalid,

filename: packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
patch: @@ -147,8 +147,8 @@ function getNodeWithoutReactNamespace(
   return node;
 }
 
-function isUseEffectIdentifier(node: Node): boolean {
-  return node.type === 'Identifier' && node.name === 'useEffect';
+function isEffectIdentifier(node: Node): boolean {
+  return node.type === 'Identifier' && (node.name === 'useEffect' || node.name === 'useLayoutEffect' || node.name === 'useInsertionEffect');
 }
 function isUseEffectEventIdentifier(node: Node): boolean {
   if (__EXPERIMENTAL__) {
@@ -726,7 +726,7 @@ const rule = {
         // Check all `useEffect` and `React.useEffect`, `useEffectEvent`, and `React.useEffectEvent`
         const nodeWithoutNamespace = getNodeWithoutReactNamespace(node.callee);
         if (
-          (isUseEffectIdentifier(nodeWithoutNamespace) ||
+          (isEffectIdentifier(nodeWithoutNamespace) ||
             isUseEffectEventIdentifier(nodeWithoutNamespace)) &&
           node.arguments.length > 0
         ) {