From 991626529299d4f535d4bb23c5dd7dd49af2edc5 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Sun, 13 Oct 2024 13:48:08 +0200 Subject: [PATCH 1/2] [ESLint] Allow `useId` in async functions `useId` is the only Hook allowed in React Server Components so it should be allowed in async functions. --- .../__tests__/ESLintRulesOfHooks-test.js | 23 +++++++++++++++++++ .../src/RulesOfHooks.js | 17 +++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index a98d2b76961f7..f7eda12148b67 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -550,6 +550,21 @@ const tests = { // TODO: this should error but doesn't. // errors: [genericError('useState')], }, + { + code: normalizeIndent` + async function Page() { + useId(); + React.useId(); + } + `, + }, + { + code: normalizeIndent` + async function useAsyncHook() { + useId(); + } + `, + }, ], invalid: [ { @@ -1129,6 +1144,14 @@ const tests = { `, errors: [asyncComponentHookError('useState')], }, + { + code: normalizeIndent` + async function notAHook() { + useId(); + } + `, + errors: [functionError('useId', 'notAHook')], + }, { code: normalizeIndent` Hook.use(); diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js index 97e72f01e4ada..0ffe6aaf1a3cb 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js @@ -40,6 +40,21 @@ function isHook(node) { } } +const serverComponentHooks = new Set(['useId']); +function isServerComponentHook(node) { + if (node.type === 'Identifier') { + return serverComponentHooks.has(node.name); + } else if ( + node.type === 'MemberExpression' && + !node.computed && + node.property.type === 'Identifier' + ) { + return serverComponentHooks.has(node.property.name); + } else { + return false; + } +} + /** * Checks if the node is a React component name. React component names must * always start with an uppercase letter. @@ -504,7 +519,7 @@ export default { if (isDirectlyInsideComponentOrHook) { // Report an error if the hook is called inside an async function. const isAsyncFunction = codePathNode.async; - if (isAsyncFunction) { + if (isAsyncFunction && !isServerComponentHook(hook)) { context.report({ node: hook, message: From 6a590b63d8fb062276c7d59dba766e705279d08a Mon Sep 17 00:00:00 2001 From: eps1lon Date: Tue, 15 Oct 2024 15:19:50 +0200 Subject: [PATCH 2/2] Shouldn't use `useId` in async Components --- .../__tests__/ESLintRulesOfHooks-test.js | 35 +++++++++++-------- .../src/RulesOfHooks.js | 17 +-------- 2 files changed, 21 insertions(+), 31 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js index f7eda12148b67..a1e4c49e15573 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js +++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js @@ -550,21 +550,6 @@ const tests = { // TODO: this should error but doesn't. // errors: [genericError('useState')], }, - { - code: normalizeIndent` - async function Page() { - useId(); - React.useId(); - } - `, - }, - { - code: normalizeIndent` - async function useAsyncHook() { - useId(); - } - `, - }, ], invalid: [ { @@ -1144,6 +1129,26 @@ const tests = { `, errors: [asyncComponentHookError('useState')], }, + { + code: normalizeIndent` + async function Page() { + useId(); + React.useId(); + } + `, + errors: [ + asyncComponentHookError('useId'), + asyncComponentHookError('React.useId'), + ], + }, + { + code: normalizeIndent` + async function useAsyncHook() { + useId(); + } + `, + errors: [asyncComponentHookError('useId')], + }, { code: normalizeIndent` async function notAHook() { diff --git a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js index 0ffe6aaf1a3cb..97e72f01e4ada 100644 --- a/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js +++ b/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js @@ -40,21 +40,6 @@ function isHook(node) { } } -const serverComponentHooks = new Set(['useId']); -function isServerComponentHook(node) { - if (node.type === 'Identifier') { - return serverComponentHooks.has(node.name); - } else if ( - node.type === 'MemberExpression' && - !node.computed && - node.property.type === 'Identifier' - ) { - return serverComponentHooks.has(node.property.name); - } else { - return false; - } -} - /** * Checks if the node is a React component name. React component names must * always start with an uppercase letter. @@ -519,7 +504,7 @@ export default { if (isDirectlyInsideComponentOrHook) { // Report an error if the hook is called inside an async function. const isAsyncFunction = codePathNode.async; - if (isAsyncFunction && !isServerComponentHook(hook)) { + if (isAsyncFunction) { context.report({ node: hook, message: