diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
index 1690f20ede490..7cb3ef0495341 100644
--- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
+++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
@@ -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
App
;
+ }
+ `,
+ errors: [tryCatchUseError('use')],
+ },
+ {
+ code: normalizeIndent`
+ function App({p1, p2}) {
+ try {
+ doSomething();
+ } catch {
+ use(p1);
+ }
+ use(p2);
+ return App
;
+ }
+ `,
+ 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 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 = [];
diff --git a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
index 8d42b319b4976..f0a2ffbda9e18 100644
--- a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
+++ b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
@@ -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 (