Skip to content

fix(react-call): Fast Refresh accepts Callables in the published bundle#111

Merged
desko27 merged 1 commit into
mainfrom
fix/hmr-fast-refresh-minified-name
Jun 21, 2026
Merged

fix(react-call): Fast Refresh accepts Callables in the published bundle#111
desko27 merged 1 commit into
mainfrom
fix/hmr-fast-refresh-minified-name

Conversation

@desko27

@desko27 desko27 commented Jun 21, 2026

Copy link
Copy Markdown
Owner

Summary

Editing a Callable's own source while a call is open closed the open call (the dialog disappeared) and Vite logged Could not Fast Refresh ("X" export is incompatible) — for real consumers, even with displayName set or the react-call/vite plugin enabled. Closes #110.

Root cause

React Fast Refresh decides component-ness via react-refresh's isLikelyComponentType, which for a function gates on /^[A-Z]/.test(fn.name || fn.displayName).

ADR-0009 made the Callable a function (Root via Object.assign) to clear that bar — but the bar is on the runtime name, and the library's own build minifies function Root to a lowercase identifier (p). So the export react-refresh sees has name === "p", fails the regex, and the boundary is rejected → full reload → the open call resets.

This is also why displayName never helped: fn.name || fn.displayName short-circuits on the truthy minified fn.name before displayName is read. And why it slipped through CI for a whole release line: every playground aliases react-call to source (ADR-0005), where Root keeps its uppercase name and Fast Refresh accepts. The bug only exists in the minified artifact consumers install.

Fix

createCallable pins an uppercase name on the returned Callable inside the existing dev-only block:

Object.defineProperty(callable, 'name', { value: 'Root', configurable: true })

Dev-only, so production output is byte-for-byte unchanged (DCE, per ADR-0011; size-limit main.js stays 810 B). Decision recorded in ADR-0022; ADR-0009 gets a header note correcting its framing (the function shape is necessary but not sufficient for the published artifact).

After the fix, Fast Refresh acceptance is unconditional and the displayName/registry layer (ADR-0010/0011/0012) is purely about HMR state persistence — orthogonal concerns.

Testing

  • Guard test in the dist vitest project (fast-refresh-compat.test.ts): imports the built dist/main.js and asserts a verbatim copy of isLikelyComponentType accepts the Callable. 🔴 against the old dist (name === "p"), 🟢 after the fix. A source-level test cannot catch this — which is exactly the blind spot that let issue Hot Reload Issues #31's original fix regress.
  • End-to-end: a consumer app importing the real minified dist with Vite 8 + @vitejs/plugin-react 6 — no incompatible warning, dialog survives edits, JSX hot-updates in place.
  • pnpm lint, check:types, unit (137) and dist (7) suites all green.

@vercel

vercel Bot commented Jun 21, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-call Ready Ready Preview, Comment Jun 21, 2026 9:37pm

The minified bundle renamed the returned component function to a
lowercase identifier, failing react-refresh's isLikelyComponentType
name check. The truthy minified `name` also shadowed `displayName`, so
neither a manual displayName nor the react-call/vite plugin could work
around it — editing a Callable's own source closed any open call via a
full reload.

createCallable now pins an uppercase `name` on the Callable (dev-only;
production output is byte-for-byte unchanged). A guard test in the dist
vitest project asserts the built artifact passes isLikelyComponentType;
a source-level test cannot, since source keeps the `Root` name. That
test loads the built dist through a runtime dynamic import (typed against
the source barrel) so it type-checks without the build present.

Refs ADR-0022. Closes #110.
@desko27 desko27 force-pushed the fix/hmr-fast-refresh-minified-name branch from 2b889e6 to 46a7013 Compare June 21, 2026 21:37
@desko27 desko27 closed this Jun 21, 2026
@desko27 desko27 reopened this Jun 21, 2026
@desko27 desko27 merged commit 4445a50 into main Jun 21, 2026
14 checks passed
@desko27 desko27 deleted the fix/hmr-fast-refresh-minified-name branch June 21, 2026 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Callable closes when saving own source despite displayName or vite plugin

1 participant