Skip to content

Commit 635514f

Browse files
CopilotDunqing
andcommitted
feat(transformer): Support nested member expressions in React Fast Refresh
Add support for detecting hooks called via nested member expressions like `FancyHook.property.useNestedThing()` in React Fast Refresh. Previously, only direct hook calls and single-level member expressions were supported. This caused nested member expression hooks to force a full reset on HMR instead of preserving state. This implementation matches React PR #35318: facebook/react#35318 Co-authored-by: Dunqing <[email protected]>
1 parent 5c320fe commit 635514f

File tree

3 files changed

+44
-12
lines changed
  • crates/oxc_transformer/src/jsx
  • tasks/transform_conformance/tests/babel-plugin-transform-react-jsx/test/fixtures/refresh/react-refresh/generates-valid-signature-for-nested-ways-to-call-hooks

3 files changed

+44
-12
lines changed

crates/oxc_transformer/src/jsx/refresh.rs

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -335,13 +335,23 @@ impl<'a> Traverse<'a, TransformState<'a>> for ReactRefresh<'a, '_> {
335335

336336
if !is_builtin_hook(&hook_name) {
337337
// Check if a corresponding binding exists where we emit the signature.
338-
let (binding_name, is_member_expression) = match &call_expr.callee {
339-
Expression::Identifier(ident) => (Some(ident.name), false),
338+
// Extract the root binding name and member path for custom hooks
339+
let (binding_name, member_path) = match &call_expr.callee {
340+
Expression::Identifier(ident) => (Some(ident.name), None),
340341
Expression::StaticMemberExpression(member) => {
341342
if let Expression::Identifier(object) = &member.object {
342-
(Some(object.name), true)
343+
// Simple member: FancyHook.useHook
344+
(Some(object.name), Some(vec![hook_name]))
345+
} else if let Expression::StaticMemberExpression(nested_member) = &member.object
346+
{
347+
// Nested member: FancyHook.property.useHook
348+
if let Expression::Identifier(object) = &nested_member.object {
349+
(Some(object.name), Some(vec![nested_member.property.name, hook_name]))
350+
} else {
351+
(None, None)
352+
}
343353
} else {
344-
(None, false)
354+
(None, None)
345355
}
346356
}
347357
_ => unreachable!(),
@@ -362,14 +372,16 @@ impl<'a> Traverse<'a, TransformState<'a>> for ReactRefresh<'a, '_> {
362372
ReferenceFlags::Read,
363373
);
364374

365-
if is_member_expression {
366-
// binding_name.hook_name
367-
expr = Expression::from(ctx.ast.member_expression_static(
368-
SPAN,
369-
expr,
370-
ctx.ast.identifier_name(SPAN, hook_name),
371-
false,
372-
));
375+
if let Some(path) = member_path {
376+
// Build the full member expression chain
377+
for property_name in path {
378+
expr = Expression::from(ctx.ast.member_expression_static(
379+
SPAN,
380+
expr,
381+
ctx.ast.identifier_name(SPAN, property_name),
382+
false,
383+
));
384+
}
373385
}
374386
expr
375387
}),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import FancyHook from 'fancy';
2+
3+
export default function App() {
4+
const foo = FancyHook.property.useNestedThing();
5+
return <h1>{foo}</h1>;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import FancyHook from "fancy";
2+
import { jsx as _jsx } from "react/jsx-runtime";
3+
var _s = $RefreshSig$();
4+
export default function App() {
5+
_s();
6+
const foo = FancyHook.property.useNestedThing();
7+
return /* @__PURE__ */ _jsx("h1", { children: foo });
8+
}
9+
_s(App, "useNestedThing{foo}", false, function () {
10+
return [FancyHook.property.useNestedThing];
11+
});
12+
_c = App;
13+
var _c;
14+
$RefreshReg$(_c, "App");

0 commit comments

Comments
 (0)