From 7bb66d178298a40a8f36ebb16fe39ebc5d98ecd1 Mon Sep 17 00:00:00 2001 From: MorikawaSouma Date: Wed, 18 Mar 2026 12:54:26 +0800 Subject: [PATCH 1/3] fix(eslint-plugin-react-hooks): support Unicode component and hook names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The rules-of-hooks rule previously only recognized ASCII uppercase letters (A-Z) for component names and hook names, causing false positives for components/hooks with Unicode names like "ÄndraVärde". This change: - Updates isComponentName() to use toUpperCase() comparison - Updates isHookName() to support Unicode uppercase letters - Updates isHook() PascalCase namespace check to support Unicode The toUpperCase()/toLowerCase() comparison approach correctly identifies uppercase letters across all Unicode scripts, not just Latin A-Z. Fixes #31446 --- .../src/rules/RulesOfHooks.ts | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts index ca82c99e2f55..3489436c44fb 100644 --- a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts +++ b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts @@ -23,11 +23,25 @@ import CodePathAnalyzer from '../code-path-analysis/code-path-analyzer'; import {getAdditionalEffectHooksFromSettings} from '../shared/Utils'; /** - * Catch all identifiers that begin with "use" followed by an uppercase Latin + * Catch all identifiers that begin with "use" followed by an uppercase * character to exclude identifiers like "user". + * + * We use toUpperCase() comparison to support Unicode letters, not just A-Z. */ function isHookName(s: string): boolean { - return s === 'use' || /^use[A-Z0-9]/.test(s); + if (s === 'use') { + return true; + } + if (s.length < 4 || !s.startsWith('use')) { + return false; + } + const fourthChar = s[3]; + // Check if it's a digit or an uppercase letter (including Unicode) + const isDigit = fourthChar >= '0' && fourthChar <= '9'; + const isUpperCaseLetter = + fourthChar === fourthChar.toUpperCase() && + fourthChar !== fourthChar.toLowerCase(); + return isDigit || isUpperCaseLetter; } /** @@ -43,8 +57,15 @@ function isHook(node: Node): boolean { isHook(node.property) ) { const obj = node.object; - const isPascalCaseNameSpace = /^[A-Z].*/; - return obj.type === 'Identifier' && isPascalCaseNameSpace.test(obj.name); + // Check if namespace starts with uppercase letter (including Unicode) + if (obj.type === 'Identifier') { + const firstChar = obj.name[0]; + return ( + firstChar === firstChar.toUpperCase() && + firstChar !== firstChar.toLowerCase() + ); + } + return false; } else { return false; } @@ -53,9 +74,17 @@ function isHook(node: Node): boolean { /** * Checks if the node is a React component name. React component names must * always start with an uppercase letter. + * + * We use toUpperCase() comparison to support Unicode letters, not just A-Z. + * This allows component names like "ÄndraVärde" to be recognized as valid + * React components. */ function isComponentName(node: Node): boolean { - return node.type === 'Identifier' && /^[A-Z]/.test(node.name); + return ( + node.type === 'Identifier' && + node.name[0] === node.name[0].toUpperCase() && + node.name[0] !== node.name[0].toLowerCase() + ); } function isReactFunction(node: Node, functionName: string): boolean { From 1bfde50f048567dbc8c8b47c0c996f725c17b7b0 Mon Sep 17 00:00:00 2001 From: MorikawaSouma Date: Wed, 18 Mar 2026 14:06:46 +0800 Subject: [PATCH 2/3] Fix #17355: Should not already be working error in Firefox after breakpoint/alert --- .../react-reconciler/src/ReactFiberWorkLoop.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index d055b271ad77..bfbf1404457f 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -1120,7 +1120,12 @@ export function performWorkOnRoot( forceSync: boolean, ): void { if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { - throw new Error('Should not already be working.'); + // There's already work running on this stack. This can happen + // in Firefox when an alert/debugger/confirm blocks the main thread + // but a message event still fires, causing a reentrant render. + // Instead of throwing, we exit early and let the original render + // finish and reschedule a new render when it's done. + return; } if (enableProfilerTimer && enableComponentPerformanceTrack) { @@ -3512,7 +3517,12 @@ function completeRoot( flushRenderPhaseStrictModeWarningsInDEV(); if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { - throw new Error('Should not already be working.'); + // There's already work running on this stack. This can happen + // in Firefox when an alert/debugger/confirm blocks the main thread + // but a message event still fires, causing a reentrant commit. + // Instead of throwing, we exit early and let the original commit + // finish when the user dismisses the modal. + return; } if (enableProfilerTimer && enableComponentPerformanceTrack) { From a744254eb4eabbace326afb341787d13c468e100 Mon Sep 17 00:00:00 2001 From: MorikawaSouma Date: Wed, 18 Mar 2026 14:11:59 +0800 Subject: [PATCH 3/3] Fix #36016: eslint-plugin-react-hooks Typescript types are broken --- .../eslint-plugin-react-hooks/src/index.ts | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/eslint-plugin-react-hooks/src/index.ts b/packages/eslint-plugin-react-hooks/src/index.ts index 924299d89894..453a912f62b1 100644 --- a/packages/eslint-plugin-react-hooks/src/index.ts +++ b/packages/eslint-plugin-react-hooks/src/index.ts @@ -71,7 +71,16 @@ const configs = { plugins, rules: recommendedLatestRuleConfigs, }, - flat: {} as { + flat: { + recommended: { + plugins: {'react-hooks': plugin}, + rules: recommendedRuleConfigs, + }, + 'recommended-latest': { + plugins: {'react-hooks': plugin}, + rules: recommendedLatestRuleConfigs, + }, + } satisfies { recommended: ReactHooksFlatConfig; 'recommended-latest': ReactHooksFlatConfig; }, @@ -86,15 +95,4 @@ const plugin = { configs, }; -Object.assign(configs.flat, { - 'recommended-latest': { - plugins: {'react-hooks': plugin}, - rules: configs['recommended-latest'].rules, - }, - recommended: { - plugins: {'react-hooks': plugin}, - rules: configs.recommended.rules, - }, -}); - export default plugin;