Repository: facebook/react
Pull request: [eslint] Disallow use within try/catch blocks




Follow up to #34032. The linter now ensures that `use` cannot be used within try/catch.


Changed files:
filename: packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
patch: @@ -1324,6 +1324,34 @@ const allTests = {
       `,
       errors: [asyncComponentHookError('use')],
     },
+    {
+      code: normalizeIndent`
+        function App({p1, p2}) {
+          try {
+            use(p1);
+          } catch (error) {
+            console.error(error);
+          }
+          use(p2);
+          return <div>App</div>;
+        }
+      `,
+      errors: [tryCatchUseError('use')],
+    },
+    {
+      code: normalizeIndent`
+        function App({p1, p2}) {
+          try {
+            doSomething();
+          } catch {
+            use(p1);
+          }
+          use(p2);
+          return <div>App</div>;
+        }
+      `,
+      errors: [tryCatchUseError('use')],
+    },
   ],
 };
 
@@ -1383,7 +1411,7 @@ if (__EXPERIMENTAL__) {
           const onEvent = useEffectEvent((text) => {
             console.log(text);
           });
-          
+
           useEffect(() => {
             onEvent('Hello world');
           });
@@ -1421,7 +1449,7 @@ if (__EXPERIMENTAL__) {
           });
           return <Child onClick={() => onClick()} />
         }
-          
+
         // The useEffectEvent function shares an identifier name with the above
         function MyLastComponent({theme}) {
           const onClick = useEffectEvent(() => {
@@ -1573,6 +1601,12 @@ function asyncComponentHookError(fn) {
   };
 }
 
+function tryCatchUseError(fn) {
+  return {
+    message: `React Hook "${fn}" cannot be called in a try/catch block.`,
+  };
+}
+
 // For easier local testing
 if (!process.env.CI) {
   let only = [];

filename: packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
patch: @@ -7,7 +7,13 @@
 /* eslint-disable no-for-of-loops/no-for-of-loops */
 
 import type {Rule, Scope} from 'eslint';
-import type {CallExpression, DoWhileStatement, Node} from 'estree';
+import type {
+  CallExpression,
+  CatchClause,
+  DoWhileStatement,
+  Node,
+  TryStatement,
+} from 'estree';
 
 // @ts-expect-error untyped module
 import CodePathAnalyzer from '../code-path-analysis/code-path-analyzer';
@@ -111,6 +117,18 @@ function isInsideDoWhileLoop(node: Node | undefined): node is DoWhileStatement {
   return false;
 }
 
+function isInsideTryCatch(
+  node: Node | undefined,
+): node is TryStatement | CatchClause {
+  while (node) {
+    if (node.type === 'TryStatement' || node.type === 'CatchClause') {
+      return true;
+    }
+    node = node.parent;
+  }
+  return false;
+}
+
 function isUseEffectEventIdentifier(node: Node): boolean {
   if (__EXPERIMENTAL__) {
     return node.type === 'Identifier' && node.name === 'useEffectEvent';
@@ -532,6 +550,16 @@ const rule = {
               continue;
             }
 
+            // Report an error if use() is called inside try/catch.
+            if (isUseIdentifier(hook) && isInsideTryCatch(hook)) {
+              context.report({
+                node: hook,
+                message: `React Hook "${getSourceCode().getText(
+                  hook,
+                )}" cannot be called in a try/catch block.`,
+              });
+            }
+
             // Report an error if a hook may be called more then once.
             // `use(...)` can be called in loops.
             if (