[ESLint] Disallow hooks in async functions (#27045)
Hooks cannot be called in async functions, on either the client or the server. This mistake sometimes happens when using Server Components, especially when refactoring a Server Component to a Client Component. React logs a warning at runtime, but it's even better to catch this with a lint rule since it will show immediate inline feedback in the editor. I added this to the existing "Rules of Hooks" ESLint rule.
Andrew Clark committed
Jul 5, 2023 at 11:44 UTC
7118f5dd7bf5f1c44d0d2944ef8ad58e423909ad
2 files changed
+41
packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
+30
@@ -1042,6 +1042,22 @@ const tests = {
1042
`,
1043
errors: [classError('useState')],
1044
},
1045
+ {
1046
+ code: normalizeIndent`
1047
+ async function AsyncComponent() {
1048
+ useState();
1049
+ }
1050
+ `,
1051
+ errors: [asyncComponentHookError('useState')],
1052
+ },
1053
+ {
1054
+ code: normalizeIndent`
1055
+ async function useAsyncHook() {
1056
+ useState();
1057
+ }
1058
+ `,
1059
+ errors: [asyncComponentHookError('useState')],
1060
+ },
1061
],
1062
};
1063
@@ -1300,6 +1316,14 @@ if (__EXPERIMENTAL__) {
1316
`,
1317
errors: [classError('use')],
1318
},
1319
+ {
1320
+ code: normalizeIndent`
1321
+ async function AsyncComponent() {
1322
+ use();
1323
+ }
1324
+ `,
1325
+ errors: [asyncComponentHookError('use')],
1326
+ },
1327
];
1328
}
1329
@@ -1368,6 +1392,12 @@ function useEffectEventError(fn) {
1392
};
1393
}
1394
1395
+function asyncComponentHookError(fn) {
1396
+ return {
1397
+ message: `React Hook "${fn}" cannot be called in an async function.`,
1398
+ };
1399
+}
1400
+
1401
// For easier local testing
1402
if (!process.env.CI) {
1403
let only = [];
packages/eslint-plugin-react-hooks/src/RulesOfHooks.js
+11
@@ -485,6 +485,17 @@ export default {
485
// Pick a special message depending on the scope this hook was
486
// called in.
487
if (isDirectlyInsideComponentOrHook) {
488
+ // Report an error if the hook is called inside an async function.
489
+ const isAsyncFunction = codePathNode.async;
490
+ if (isAsyncFunction) {
491
+ context.report({
492
+ node: hook,
493
+ message:
494
+ `React Hook "${context.getSource(hook)}" cannot be ` +
495
+ 'called in an async function.',
496
+ });
497
+ }
498
+
499
// Report an error if a hook does not reach all finalizing code
500
// path segments.
501
//