Skip to content

Fix Fast Refresh state loss when editing files that call constate(...)#179

Closed
diegohaz wants to merge 1 commit into
mainfrom
fix-146-fast-refresh-state-loss
Closed

Fix Fast Refresh state loss when editing files that call constate(...)#179
diegohaz wants to merge 1 commit into
mainfrom
fix-146-fast-refresh-state-loss

Conversation

@diegohaz
Copy link
Copy Markdown
Owner

Summary

  • In dev (process.env.NODE_ENV !== "production"), constate(useValue, ...) now caches its returned Provider and hooks so that an HMR re-evaluation of the caller's module reuses the same component identities. Without this, the new module evaluation produced fresh references, React unmounted the existing Provider, all state was lost, and (depending on the file's exports) Vite would fall back to a full page reload or report "useCounterContext" export is incompatible. Closes React Fast Refresh doesn't work on Constate side #146.
  • The cache key is the caller's file:line:column (captured from new Error().stack after skipping constate's own frames by both function name and file path — covering node_modules/constate/dist/index.js, node_modules/.vite/deps/constate*.js, and yarn-pnp .zip layouts) combined with a structural signature of useValue and selectors: name, selector count, selector names, and hook counts. The cached Provider reads useValue and selectors through internal refs, so edits to the hook body or selector bodies take effect on the next render while React preserves the Provider's fiber and its hook state. Adding or removing hook calls in useValue or any selector invalidates the cache entry, preventing React's "rendered more hooks" error at the cost of state loss for that one structural edit.
  • Production builds skip the cache entirely; the only runtime overhead is 1 + selectorCount ref dereferences per Provider render.

Test plan

  • pnpm test — 18 tests pass (the 12 pre-existing tests plus 6 new ones covering identity reuse on matching signature, invalidation on useValue hook-count change, invalidation on selector hook-count change, independence of two calls on different lines, independence of two calls on the same line, and the cache-skip for anonymous useValue).
  • pnpm lint — clean.
  • pnpm tsc — clean.
  • pnpm format — clean.
  • Manual: in examples/src/counter/App.jsx, click the counter a few times, edit prevCount + 1 to prevCount + N, observe that the count stays at its pre-edit value and the next click applies the new increment. Verified with Vite 8 + React 19; count went from 3 to 103 after changing + 1 to + 100.

🤖 Generated with Claude Code

In dev, cache the Provider and hooks returned by `constate(...)` so HMR
re-evaluation of the caller's module reuses the same component identities
instead of remounting and losing state. Closes #146.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 27, 2026

Open in StackBlitz

npm i https://pkg.pr.new/constate@179

commit: 271e12b

@diegohaz diegohaz closed this May 27, 2026
@diegohaz diegohaz deleted the fix-146-fast-refresh-state-loss branch May 27, 2026 22:06
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.

React Fast Refresh doesn't work on Constate side

1 participant