Conversation
…ule imports
Third-party libraries like amCharts export objects with methods named
`useTheme()`, `useData()`, etc. that are not React hooks. The React
Compiler's ESLint plugin incorrectly flags these as hook violations
because the hook detection heuristic treats any `use*`-named property
as a potential hook.
This change tracks identifiers loaded from module imports (ImportDefault,
ImportNamespace, ImportSpecifier) whose types are unresolved by the type
system. For MethodCall instructions where the receiver is one of these
untyped imports, we skip hook validation since the `use*` property was
typed heuristically based on naming alone, not from actual type info.
This preserves correct behavior for:
- `import React from 'react'; React.useState()` (typed receiver)
- `const React = require('react'); React.useState()` (ModuleLocal, not import)
- Direct hook calls like `useHook()` (CallExpression, not MethodCall)
Fixes #32109
Greptile SummaryThis PR fixes false hook-violation errors from the React Compiler's ESLint plugin when third-party libraries (e.g., amCharts) export objects with
Confidence Score: 4/5
Important Files Changed
Last reviewed commit: ec06091 |
| * For MethodCall on untyped Global receivers, skip hook validation | ||
| * as the property was typed heuristically. See #32109. | ||
| */ | ||
| if (instr.value.kind === 'MethodCall') { | ||
| const receiverType = instr.value.receiver.identifier.type; | ||
| if ( | ||
| receiverType.kind !== 'Object' && | ||
| receiverType.kind !== 'Function' | ||
| ) { | ||
| break; | ||
| } |
There was a problem hiding this comment.
Broader skip condition than validateHooksUsage
In the main validateHooksUsage function (line 381), the skip check requires both conditions: isReceiverUntyped && isReceiverImport. Here in visitFunctionExpression, only the type check is applied — any MethodCall with a non-Object/non-Function receiver is skipped, regardless of whether it's an import.
This is technically safe because getHookKind() only returns non-null for identifiers with type.kind === 'Function', so the break is effectively redundant with what getHookKind would do. However, the asymmetry between the two functions makes the intent less clear. The comment says "untyped Global receivers" but the check is broader than just globals — it applies to any receiver with an unresolved type (e.g., locals, parameters).
Consider adding a brief comment clarifying why the import check is unnecessary here (i.e., because getHookKind only operates on Function-typed identifiers, the type check alone is sufficient).
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Prompt To Fix With AI
This is a comment left during a code review.
Path: compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts
Line: 493-503
Comment:
**Broader skip condition than `validateHooksUsage`**
In the main `validateHooksUsage` function (line 381), the skip check requires **both** conditions: `isReceiverUntyped && isReceiverImport`. Here in `visitFunctionExpression`, only the type check is applied — any MethodCall with a non-Object/non-Function receiver is skipped, regardless of whether it's an import.
This is technically safe because `getHookKind()` only returns non-null for identifiers with `type.kind === 'Function'`, so the `break` is effectively redundant with what `getHookKind` would do. However, the asymmetry between the two functions makes the intent less clear. The comment says "untyped Global receivers" but the check is broader than just globals — it applies to any receiver with an unresolved type (e.g., locals, parameters).
Consider adding a brief comment clarifying why the import check is unnecessary here (i.e., because `getHookKind` only operates on `Function`-typed identifiers, the type check alone is sufficient).
<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>
How can I resolve this? If you propose a fix, please make it concise.
Mirror of facebook/react#35858
Original author: sleitor
Summary
Third-party libraries like amCharts export objects with methods named
useTheme(),useData(), etc. that are not React hooks. The React Compiler's ESLint plugin incorrectly flags these as hook violations because the hook detection heuristic treats anyuse*-named property as a potential hook.This change tracks identifiers loaded from module imports (
ImportDefault,ImportNamespace,ImportSpecifier) whose types are unresolved by the type system. ForMethodCallinstructions where the receiver is one of these untyped imports, we skip hook validation since theuse*property was typed heuristically based on naming alone, not from actual type info.How it works
In
ValidateHooksUsage.ts:Track untyped import identifiers: During
LoadGlobalprocessing, we record identifiers that come from import bindings but have unresolved types (notObjectorFunctionwith known shapes).Skip hook validation for MethodCalls on untyped imports: When processing a
MethodCall, if the receiver is an untyped import, we skip the hook conditional/dynamic usage checks.Handle nested function expressions: In
visitFunctionExpression, we similarly skip hook-in-callback validation for method calls on untyped receivers.What's preserved
import React from 'react'; React.useState()— React has a known type, so hook validation is preserved ✅const React = require('react'); React.useState()—Reactis aModuleLocalbinding (not an import), so it's not tracked as an untyped import and hook validation is preserved ✅useHook()— These areCallExpressions, notMethodCalls, so they're unaffected ✅import {useHook} from 'some-lib'; useHook()— Direct import of a hook-named function still validates correctly ✅Test plan
amCharts.useTheme()in conditional blocks no longer errorsReact.useState()in conditional blocks still errors (both import and require styles)useHook()calls in conditional blocks still errorFixes #32109