use-hot-module-reload
React hook that fires a callback after any hot-module reload completes, for Webpack and Vite.
Repository Health
Technical Analysis
use-hot-module-reload is a tiny React hook that runs a callback whenever hot-module reloading completes — not just for the module that called the hook, but for any HMR update anywhere in the app. It exists for the common development-time problem where React state or memoized values go stale because they don’t automatically recompute when some other, unrelated module changes underneath them.
The library ships dual builds for Webpack (both its ESM and CommonJS hot APIs) and Vite, auto-detecting whichever bundler’s HMR runtime is present at call time and safely falling back to a no-op in production or unsupported bundlers, so it never needs to be conditionally imported or stripped out before shipping.
What You Get
- A single
useHotModuleReload(callback)hook with zero configuration - Automatic detection of Webpack’s ESM and CommonJS hot APIs as well as Vite’s HMR runtime
- Dual CJS/ESM package builds produced with tsup, plus full TypeScript typings
- Safe no-op behavior in production builds and on unsupported bundlers
Common Use Cases
- Recomputing derived or memoized values that don’t invalidate automatically when an unrelated module hot-reloads
- Resetting local component state after editing a reducer, context provider, or utility module during development
- Forcing a re-render to reflect updated constants or configuration pulled in from a sibling module
Under The Hood
Architecture
The package exposes the same useHotModuleReload(callback) function from two parallel entry points — use-hot-module-reload.mts for ESM consumers and use-hot-module-reload.cts for CommonJS — selected via the package’s exports map rather than a single shared implementation. Both files follow the same shape: a module-level feature-detection check (isModule/hasHMR in the ESM build, a module.hot check in the CJS build) gates a useEffect-based subscription that registers a status handler with whichever bundler’s HMR API is present and tears it down on cleanup. The ESM build further branches at call time between a Webpack path (import.meta.webpackHot) and a Vite path (import.meta.hot), since Vite exposes no equivalent to Webpack’s simple idle-status event; instead it polls a window.__vite_plugin_react_timeout global set by vite-plugin-react on a 10ms interval, bounded by a 1000ms fallback timeout, to approximate when React has actually flushed the update. This makes the Vite branch meaningfully more fragile than the Webpack branch, since it depends on an undocumented plugin internal rather than a public event.
Tech Stack
Written in TypeScript against a react peer dependency (>=17.0.0) with no runtime dependencies of its own. The package lives inside a Yarn workspaces monorepo alongside a fixtures/ directory of standalone sample apps — separate Webpack (ESM and CJS), Vite, Parcel, and Jest (ESM and CJS) projects — used to manually exercise the hook against each bundler’s real HMR implementation. Builds are produced by tsup, run twice via two separate configs (tsup.cjs.ts and tsup.mjs.ts) to emit both a .cjs and a .js (ESM) artifact plus declaration files, orchestrated with concurrently.
Code Quality
There are no automated unit tests exercising the hook’s actual HMR-triggering logic; the jest-cjs and jest-esm fixtures only assert that the package resolves and renders without throwing under Jest’s CJS and ESM module systems, which is a smoke test rather than a behavioral test. There is no CI configuration in the repository, so these fixture checks (and the bundler fixtures generally) appear to be run manually rather than on every change. The source itself is small, consistently typed, and includes unusually thorough inline comments explaining the non-obvious Vite polling workaround, which meaningfully offsets the lack of automated coverage.
API Design The public surface is a single hook taking one callback argument, with sensible zero-configuration defaults — no setup, no provider, no bundler-specific import path for consumers to choose. It fails safe (silently becomes a no-op) rather than throwing when no supported HMR runtime is detected, which keeps it safe to leave in code that also runs in production. This narrow, single-function API keeps the learning curve close to zero at the cost of very little flexibility, which is an appropriate tradeoff for a development-only escape hatch.