[compiler] Fix use hook resolution for aliased React imports#36149
[compiler] Fix use hook resolution for aliased React imports#36149cyphercodes wants to merge 1 commit intofacebook:mainfrom
Conversation
When importing React with an alias (e.g., import ReactAlias from 'react'), the compiler was not correctly resolving the 'use' hook type. This caused the compiled output to place the hook call inside a conditional cache block, violating the Rules of Hooks. The fix ensures that default imports from 'react' always resolve to the React global type, regardless of the local binding name. Fixes facebook#36137
mahdirajaee
left a comment
There was a problem hiding this comment.
Good catch. The core issue is clear: when someone writes import ReactAlias from 'react' and then calls ReactAlias.use(), the compiler was trying to look up the global type using ReactAlias as the key (which doesn't exist in the globals map) instead of resolving it to the canonical React global.
The fix is targeted and makes sense:
-
The condition is specific enough —
binding.kind === 'ImportDefault' && binding.module.toLowerCase() === 'react'ensures this only applies to default imports from thereactmodule. Named imports and namespace imports continue through the existing path, which is correct sinceimport { useState } from 'react'would already resolveuseStateby name. -
The
.toLowerCase()on the module check — is this intentional? In practice,'react'should always be lowercase in the import specifier, but if there's a case-insensitive filesystem edge case being handled here, a comment explaining that would be helpful. -
Edge case consideration — what about
import ReactAlias from 'react'followed byReactAlias.useState()? Based on the fix,ReactAliaswould resolve to theReactglobal type, so member access like.useState()should work correctly since the globalReacttype already has those members defined. The test fixture only covers.use()though — it might be worth adding a fixture for other hooks accessed through an alias to confirm. -
What about re-exports? — e.g., a local module that does
export { default } from 'react'and thenimport R from './myReactReexport'. This wouldn't be caught sincebinding.modulewouldn't be'react', but that's a separate (and much harder) problem. -
The test fixture is clean —
use-hook-aliased-import.jsdemonstrates the exact scenario, and the expected output shows the compiler correctly memoizing the result.
Looks good overall. The fix is narrowly scoped to the actual problem without overreaching.
Summary
When importing React with an alias (e.g.,
import ReactAlias from 'react'), the compiler was not correctly resolving the 'use' hook type. This caused the compiled output to place the hook call inside a conditional cache block, violating the Rules of Hooks.Problem
The issue was in the Environment.ts file where imports from known React modules are resolved. When a user imports React with a different name like:
The compiler looked up
ReactAliasin the globals map, but the React object type is stored under the key'React'. This meant that when the compiler encounteredReactAlias.use(), it didn't recognizeuseas the special built-inuseoperator.Solution
The fix ensures that default imports from 'react' always resolve to the React global type, regardless of the local binding name.
Test Plan
Added a test fixture that verifies the correct behavior when using
ReactAlias.use().Fixes #36137