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 for third-party libraries (like amCharts) that export methods with
Confidence Score: 4/5
Important Files Changed
Last reviewed commit: ea5f060 |
| * 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 logic than main function
In validateHooksUsage, the skip requires both isReceiverUntyped && isReceiverImport, but here in visitFunctionExpression only isReceiverUntyped is checked (no import tracking). This means method calls on any untyped receiver (not just imports) will skip hook validation inside nested functions — e.g., localObj.useHook() inside a callback would also be skipped if localObj has an unresolved type.
This is likely acceptable since getHookKind relies on the same heuristic type system (getPropertyType assigns DefaultNonmutatingHook for use*-named properties on untyped receivers), so the reasoning is consistent. However, the comment on line 493 says "untyped Global receivers" which is misleading — the check doesn't verify the receiver is a global, just that its type is unresolved.
Consider updating the comment to reflect the actual behavior:
| * 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; | |
| } | |
| /** | |
| * For MethodCall on receivers with unresolved types, skip hook | |
| * validation as the property was typed heuristically. See #32109. | |
| */ |
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 logic than main function**
In `validateHooksUsage`, the skip requires both `isReceiverUntyped && isReceiverImport`, but here in `visitFunctionExpression` only `isReceiverUntyped` is checked (no import tracking). This means method calls on *any* untyped receiver (not just imports) will skip hook validation inside nested functions — e.g., `localObj.useHook()` inside a callback would also be skipped if `localObj` has an unresolved type.
This is likely acceptable since `getHookKind` relies on the same heuristic type system (`getPropertyType` assigns `DefaultNonmutatingHook` for `use*`-named properties on untyped receivers), so the reasoning is consistent. However, the comment on line 493 says "untyped Global receivers" which is misleading — the check doesn't verify the receiver is a global, just that its type is unresolved.
Consider updating the comment to reflect the actual behavior:
```suggestion
/**
* For MethodCall on receivers with unresolved types, skip hook
* validation as the property was typed heuristically. See #32109.
*/
```
<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.| const receiverType = instr.value.receiver.identifier.type; | ||
| const isReceiverUntyped = | ||
| receiverType.kind !== 'Object' && | ||
| receiverType.kind !== 'Function'; | ||
| const isReceiverImport = | ||
| untypedImportIdentifiers.has( | ||
| instr.value.receiver.identifier.id, | ||
| ); | ||
| const skipHookCheck = isReceiverUntyped && isReceiverImport; |
There was a problem hiding this comment.
Redundant isReceiverUntyped check
The isReceiverUntyped check on line 374-376 is redundant with isReceiverImport on line 377-380. The untypedImportIdentifiers set (line 178) only adds identifiers that already passed the same untyped check (lines 253-254: type.kind !== 'Object' && type.kind !== 'Function'). So whenever isReceiverImport is true, isReceiverUntyped is guaranteed to also be true.
This isn't a bug — the redundancy adds a defensive check in case the identifier's type changes between LoadGlobal and MethodCall processing — but it may be worth a brief comment noting this is intentional for clarity.
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: 373-381
Comment:
**Redundant `isReceiverUntyped` check**
The `isReceiverUntyped` check on line 374-376 is redundant with `isReceiverImport` on line 377-380. The `untypedImportIdentifiers` set (line 178) only adds identifiers that already passed the same untyped check (lines 253-254: `type.kind !== 'Object' && type.kind !== 'Function'`). So whenever `isReceiverImport` is `true`, `isReceiverUntyped` is guaranteed to also be `true`.
This isn't a bug — the redundancy adds a defensive check in case the identifier's type changes between `LoadGlobal` and `MethodCall` processing — but it may be worth a brief comment noting this is intentional for clarity.
How can I resolve this? If you propose a fix, please make it concise.|
Upstream PR was closed or merged. Code is synced via branch mirror. |
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