This guide is for engineers who need to understand react-use below the surface:
library architects evaluating it as a dependency, contributors extending it, and
teams auditing it for production use. It documents the repository structure, the
recurring design patterns that make ~100 hooks consistent, the build and
distribution model, security considerations, and the project's maintenance
status.
For day-to-day hook usage, see the User Guide. This guide assumes you have read it.
1. Executive Summary
react-use is a TypeScript library that exports a flat collection of roughly 100
React Hooks from a single entry point (src/index.ts). It is published to npm as
react-use (current source version 17.6.0) under the Unlicense (public
domain).
| Attribute | Value |
|---|---|
| Type | React Hooks library (npm package) |
| Language | TypeScript (compiled to CJS + ESM) |
| Entry point | src/index.ts (barrel of named re-exports) |
| Public surface | ~100 hooks + 6 factory functions |
| Runtime deps | 14 small single-purpose libraries (see §6) |
| Peer deps | react, react-dom (version range *) |
| Build | tsc → lib/ (CJS) and esm/ (ESM) |
| Tests | Jest + Testing Library (browser + SSR configs) |
| Docs/demo | Storybook (stories/) |
| License | Unlicense (public domain) |
Important
An open maintenance issue (#2703, "Archiving react-use because it's unmaintained") signals the project is effectively in maintenance mode. The package remains installable and widely used, but new feature development has slowed. Factor this into long-term dependency decisions — see §9 Maintenance & Roadmap.
2. Architecture
2.1 The big picture
react-use has no runtime of its own — it is a set of independent hooks that run
inside the host application's React render loop. There is no central store, no
provider requirement (except where a hook explicitly creates one), and no shared
mutable global state beyond a handful of module-level caches.
┌─────────────────────────────────────┐
import { useX } │ src/index.ts │
from 'react-use' │ (barrel: named re-exports) │
│ └───────────────┬─────────────────────┘
▼ │
┌───────────────┐ each hook is a standalone module
│ Host React │ ┌─────┴──────┬───────────┬──────────┐
│ component │ ▼ ▼ ▼ ▼
│ calls useX() │ src/useX.ts src/useY.ts src/factory/ src/misc/
└───────────────┘ (uses React (composes create*() util, types,
primitives) other hooks) factories hookState)
│ │ │ │
└────────────┴────────────┴──────────┘
│
shared helpers: on/off,
isBrowser, isNavigator,
resolveHookState, isDeepEqual
Three structural facts define the architecture:
- One hook per file. Every public hook lives in its own
src/use*.ts(x)module and is re-exported by name fromsrc/index.ts. This is what makes tree-shaking effective ("sideEffects": false). - Hooks compose hooks. Higher-level hooks are built from lower-level ones —
e.g.
useAsyncwrapsuseAsyncFn;useCounterwrapsuseGetSet;useBooleanis literallyuseToggle;useNumberisuseCounter. The dependency graph is internal and acyclic. - A thin shared core. A small
src/misc/layer (event helpers, environment flags, state resolution, deep-equality) is imported across dozens of hooks and is the closest thing to a "kernel."
2.2 Module layout
| Path | Role |
|---|---|
src/index.ts |
Public barrel. The single source of truth for what is exported. |
src/use*.ts(x) |
One file per hook (~100 files). The bulk of the library. |
src/factory/ |
Factory functions that produce hooks/providers (create*). |
src/misc/ |
Shared internal helpers (not part of the public API). |
src/component/ |
A render-prop component wrapper (legacy ergonomics). |
stories/ |
Storybook stories — the live demo site and de-facto usage docs. |
tests/ |
Jest unit tests, mirroring the src/ layout. |
docs/ |
Markdown documentation per hook. |
2.3 The misc/ core
src/misc/util.ts is imported by a large share of the hooks. It provides:
export const noop = () => {};
export function on(obj, ...args) { obj?.addEventListener?.(...args); }
export function off(obj, ...args) { obj?.removeEventListener?.(...args); }
export const isBrowser = typeof window !== 'undefined';
export const isNavigator = typeof navigator !== 'undefined';
on/off are null-safe event (de)registration wrappers used by every
event-driven sensor hook (useEvent, useMouse, useScroll, useIdle,
useClickAway, …). isBrowser/isNavigator are the environment guards that
power the SSR strategy in §3.
src/misc/hookState.ts provides resolveHookState(), the helper that lets hooks
accept either a value or a (prev) => next updater function uniformly. It is the
basis of the "lazy initializer + functional setter" ergonomics in useGetSet,
useList, useCounter, useStateWithHistory, createGlobalState, and others.
export function resolveHookState(nextState, currentState?) {
if (typeof nextState === 'function') {
return nextState.length
? nextState(currentState) // (prev) => next
: nextState(); // () => initial
}
return nextState;
}
src/misc/isDeepEqual.ts re-exports fast-deep-equal/react; parseTimeRanges.ts
converts TimeRanges into plain objects for the media hooks; types.ts holds the
shared PromiseType / FunctionReturningPromise utility types.
3. Server-Side Rendering Strategy
SSR safety is a cross-cutting concern, not a single feature. Because so many hooks
touch window, navigator, or document, the library uses a consistent
export-time implementation swap: the module decides once, when it loads,
whether the relevant browser API exists, and exports either the real hook or a
no-op/mock with the same signature.
// pattern: pick the implementation at module load, not per render
export default isBatteryApiSupported ? useBattery : useBatteryMock;
export default isVibrationApiSupported ? useVibrate : noop;
export default isBrowser ? useSearchParam : useSearchParamServer;
export default (isBrowser && hasEventConstructor) ? useLocationBrowser : useLocationServer;
export default (typeof document !== 'undefined') ? useTitle : (_t) => {};
Hooks using this pattern include useBattery, useVibrate, useMediaDevices,
useSearchParam, useLocation, useTitle, useMeasure, and
useLockBodyScroll. Others guard inline instead — e.g. useLocalStorage and
useSessionStorage early-return the initial value plus no-op setters when
!isBrowser, and useMedia accepts a defaultState so the server render is
deterministic.
useIsomorphicLayoutEffect is the unifying primitive for layout effects:
const useIsomorphicLayoutEffect = isBrowser ? useLayoutEffect : useEffect;
This avoids React's "useLayoutEffect does nothing on the server" warning while
preserving layout-effect timing in the browser. Internal hooks that measure the
DOM (useCss, useMeasure-adjacent code, createGlobalState) build on it.
Note
Because the implementation is chosen at module-load time, the decision is made on the rendering environment that imports the module, not per-call. A hook imported on the server gets the server implementation for that process; the same hook in the browser bundle gets the browser implementation. There is no per-render branching cost.
Warning
A known limitation surfaces with the Next.js App Router:
useWindowSize(and otherwindow-reading hooks) can misbehave in React Server Components / the App Router because module-load-time environment detection does not map cleanly onto RSC boundaries (community issue #2532). Call these hooks only inside Client Components ('use client').
4. Design Patterns
4.1 Factory pattern (src/factory/)
Six exports are factories — functions you call once to manufacture a hook or a provider/consumer pair. This is how the library offers configurable or context-bound behavior without a runtime registry.
| Factory | Produces | Backed by |
|---|---|---|
createHTMLMediaHook(tag) |
useAudio / useVideo |
shared media element controller (internal) |
createGlobalState(initial) |
a provider-less shared-state hook | module-level store + setter list |
createReducer(...middleware) |
a useReducer hook with Redux middleware |
useRef + composed middleware chain |
createReducerContext(reducer, init) |
[useCtx, Provider, Context] |
useReducer + createContext |
createStateContext(init) |
[useCtx, Provider, Context] |
useState + createContext |
createBreakpoint(map) |
a named-breakpoint hook | resize listener + sorted thresholds |
createMemo(fn) |
a memoizing hook | useMemo keyed by args |
createHTMLMediaHook is the most substantial: a single ~240-line factory in
src/factory/createHTMLMediaHook.ts produces both useAudio and useVideo. It
owns a ref to the media element, wires the full set of media events
(onPlay, onPause, onTimeUpdate, onVolumeChange, onDurationChange,
onProgress, …) through a wrapEvent helper that preserves any user-supplied
handlers, and exposes an imperative controls object (play, pause, seek,
volume, mute, unmute). It guards .play() with a lockPlay flag to work
around the Chromium "play() request was interrupted" promise bug.
Note
Two factories exist in source but are not exported from
src/index.ts:createRouterandcreateRenderProp. They are internal/experimental and should not be relied on as public API.
4.2 Getter-based state (escaping stale closures)
Several hooks return a getter instead of (or alongside) a value, so async
callbacks always read the freshest state without re-subscribing. useGetSet,
useGetSetState, and useMountedState are the canonical examples; useLatest
provides the ref-based variant. This pattern is the library's answer to the React
"stale closure" problem in timers and async work.
4.3 Compare-effect family
useCustomCompareEffect is the base; useDeepCompareEffect and
useShallowCompareEffect specialize it with fast-deep-equal and
fast-shallow-equal respectively. All three carry development-only console.warn
guards that steer users back to React.useEffect when dependencies are absent or
all-primitive — a deliberate "pit of success" design choice.
4.4 Alias hooks
A few exports are pure aliases, kept for naming ergonomics:
useBoolean → useToggle, useNumber → useCounter. They add no behavior; they
re-export the underlying module.
5. Build, Distribution & Configuration
This library has no application configuration. Its "configuration surface" is
its build pipeline and package.json metadata.
5.1 Output formats
Per the multi-format-library pattern, the package ships two builds from the same TypeScript source:
| Field | Path | Format | Built by |
|---|---|---|---|
main |
lib/index.js |
CommonJS | tsc (build:cjs) |
module |
esm/index.js |
ES Modules | tsc -m esNext --outDir esm (build:es) |
types / typings |
lib/index.d.ts |
TypeScript decls | tsc |
"sideEffects": false permits aggressive tree-shaking, and "files": ["lib/", "esm/"]
limits the published tarball to the two builds.
5.2 npm scripts (the real operational surface)
AST extraction does not capture npm scripts, so the authoritative task list comes
straight from package.json:
| Script | Command | Purpose |
|---|---|---|
start |
yarn storybook |
Launch the Storybook dev/demo server. |
test |
jest --maxWorkers 2 |
Run the browser-jsdom test suite. |
test:ssr |
jest --config ./jest.config.node.ts |
Run tests under a Node/SSR environment. |
test:watch |
jest --watch |
Watch-mode tests. |
test:coverage |
jest --coverage |
Coverage report. |
lint |
eslint "{src,tests,stories}/**/*.{ts,tsx}" |
Lint all source. |
lint:fix |
yarn lint --fix |
Autofix lint issues. |
lint:types |
tsc --noEmit |
Type-check without emitting. |
build:cjs |
tsc |
Build the CommonJS output to lib/. |
build:es |
tsc -m esNext --outDir esm |
Build the ESM output to esm/. |
build |
yarn build:cjs && yarn build:es |
Full dual build. |
clean |
rimraf lib storybook-static esm |
Remove build artifacts. |
storybook |
start-storybook -p 6008 |
Storybook on port 6008. |
storybook:build |
build-storybook |
Static Storybook build. |
release |
semantic-release |
Automated versioned release. |
5.3 Toolchain pins
The repo pins its toolchain via volta (node 14.21.3, yarn 1.22.22) and uses
husky Git hooks: pre-commit runs lint-staged (eslint --fix on staged TS),
and pre-push runs lint && clean && build && test. Releases are fully
automated through semantic-release (changelog + npm + git plugins) on the
master and next branches.
Tip
The only repository
NODE_ENVconsumer in source is the dev/prod branching in hooks (e.g. the media-element error logging and the compare-effect warnings). There is no environment-variable configuration to manage at runtime — these guards are compiled away by the consumer's bundler in production builds.
6. Dependencies
The library is deliberately composed of small, single-purpose runtime dependencies. Document the integration, not the libraries themselves.
| Dependency | Used by | Why |
|---|---|---|
js-cookie |
useCookie |
Read/write browser cookies. |
copy-to-clipboard |
useCopyToClipboard |
Cross-browser clipboard write. |
screenfull |
useFullscreen |
Cross-browser Fullscreen API wrapper. |
nano-css |
useCss |
Runtime CSS-in-JS (CSSOM/VCSSOM addons). |
fast-deep-equal |
useDeepCompareEffect, useBattery |
Deep equality. |
fast-shallow-equal |
useShallowCompareEffect |
Shallow equality. |
resize-observer-polyfill |
useMeasureDirty |
ResizeObserver fallback. |
@xobotyi/scrollbar-width |
useScrollbarWidth |
Measure scrollbar width. |
set-harmonic-interval |
useHarmonicIntervalFn |
Synchronized intervals. |
throttle-debounce |
useIdle |
Throttling. |
ts-easing |
useTween |
Easing functions. |
react-universal-interface |
useScratch (render-prop) |
Render-prop/children API. |
tslib |
(all) | TypeScript runtime helpers. |
@types/js-cookie |
useCookie |
Type definitions. |
Optional peer integrations (NOT bundled, hooks not exported by default):
keyboardjs for useKeyboardJs, rebound for useSpring. Consumers who want
these must install the peer and import the hook directly from its source path.
7. Security Analysis
react-use runs entirely in the consumer's browser context and has no server,
no network calls of its own, and no secret handling. The security surface is
therefore (a) the data hooks read/write in browser storage, and (b) transitive
dependency risk.
7.1 Browser storage and cookies
useLocalStorage, useSessionStorage, and useCookie read and write
client-side storage. They perform no encryption or sanitization — that is the
caller's responsibility. Do not store secrets, tokens you cannot rotate, or PII in
localStorage/sessionStorage via these hooks; the values are readable by any
script on the page.
The storage hooks are defensively wrapped in try/catch to survive private-mode
and storage-restricted environments (where localStorage access throws), and
fall back to the initial value rather than crashing.
7.2 Known dependency vulnerability — js-cookie
Caution
The pinned
js-cookie@^2.2.1(used byuseCookie) is affected by a reported prototype-pollution advisory (community issue #2704, CVE-2026-46625). If your application usesuseCookie, audit the resolvedjs-cookieversion and force a patched release via your package manager's override/resolution mechanism, or avoiduseCookieand use a vetted cookie library directly.
7.3 DOM/event hooks
Hooks that attach global listeners (useEvent, useKey, useIdle,
useClickAway, usePageLeave, drag/drop hooks) register on window/document
and clean up on unmount via the on/off helpers. They do not evaluate or inject
untrusted strings. useCss generates class names from a counter (no user input in
the selector), so it does not introduce a CSS-injection vector beyond the style
object you pass it.
7.4 General guidance
- Treat every value flowing through storage/cookie hooks as untrusted on read.
- Pin and audit transitive dependencies; the library's
*peer range means React version safety is delegated to the consumer. - Production builds strip the dev-only
consolediagnostics viaNODE_ENV, so no internal state leaks to end-user consoles in production.
8. Testing & Quality
| Aspect | Detail |
|---|---|
| Runner | Jest (jest --maxWorkers 2) |
| Browser env | jsdom (default config) |
| SSR env | Node config (jest.config.node.ts, run via test:ssr) |
| Component testing | @testing-library/react, @testing-library/react-hooks |
| Mocks | @shopify/jest-dom-mocks, jest-localstorage-mock, raf-stub |
| Lint | ESLint + @typescript-eslint, Prettier, eslint-plugin-react-hooks |
| Type check | tsc --noEmit (lint:types) |
The dual browser/SSR test configuration is notable: it directly exercises the
SSR-swap strategy from §3, so regressions in
environment guards are caught by the test:ssr suite. Tests mirror the src/
tree under tests/, and the raf-stub / jest-localstorage-mock setup lets the
timer and storage hooks be tested deterministically.
Tip
When contributing a hook that touches a browser-only API, add both a browser test and an SSR test. The SSR path (mock/no-op export) is easy to break and is only covered by
yarn test:ssr.
9. Maintenance & Operational Status
Important
Issue #2703 ("Archiving react-use because it's unmaintained") is open, and related issues note the published version lags the repository (#2689) and that several long-standing bugs remain open. The library is best understood as stable and in maintenance mode rather than actively evolving.
Open issues worth tracking before adopting or upgrading:
| Issue | Area | Impact |
|---|---|---|
| #2700 | useDeepCompareEffect |
Throws when comparing objects made with Object.create(null). |
| #2684 | useCss / nano-css |
Vite import-analysis failure resolving nano-css/addon/vcssom. |
| #2675 | useScrolling |
Does not clean up its scroll-end timer. |
| #2612 | useIntersection |
Warning under React 19. |
| #2532 | useWindowSize |
Does not work under the Next.js App Router. |
| #2227 | useDeepCompareEffect |
Spurious warning for null/undefined non-primitive values. |
| #2704 | useCookie / js-cookie |
Transitive CVE (see §7.2). |
Migration considerations
- If you depend on
useCss, validate it against your bundler (Vite users: see #2684) or vendor a thin CSS-in-JS alternative. - For React 19 adopters, watch
useIntersection(#2612) and the general App-Router caveats; prefer calling sensor hooks in Client Components only. - The
*peer-dependency range means the library will install against any React version, but it is validated against React 17 in CI (react@17.0.2in devDependencies). Treat React 18/19 support as community-verified rather than CI-guaranteed.
10. Contributing
The repository is a standard TypeScript library workflow:
# 1. Install (Volta pins node 14.21.3 / yarn 1.22.22)
yarn install
# 2. Run the demo/dev environment
yarn start # Storybook on http://localhost:6008
# 3. Make your change under src/, add a story + tests
# 4. Verify locally (mirrors the pre-push hook)
yarn lint
yarn lint:types
yarn build
yarn test
yarn test:ssr # don't skip this for browser-API hooks
Conventions to follow when adding a hook:
- [ ] One hook per file under
src/, nameduseThing.ts(x). - [ ] Re-export it by name from
src/index.ts(the public barrel). - [ ] Guard any
window/navigator/documentaccess withisBrowser/isNavigatorand provide an SSR-safe export (real-vs-mock swap). - [ ] Reuse
on/offfromsrc/misc/util.tsfor event listeners and clean them up on unmount. - [ ] Use
resolveHookStatefor setters that should accept both values and updater functions. - [ ] Add a Storybook story under
stories/(this is the de-facto demo doc) and a test undertests/, including an SSR test where relevant. - [ ] Gate development diagnostics behind
process.env.NODE_ENV !== 'production'.
Commits feed semantic-release, so follow the Conventional Commits format
(commitizen is configured via git-cz) — the release type and changelog are
derived from commit messages automatically.
Note
Do not export experimental factories (
createRouter,createRenderProp) from the barrel, and remember that peer-dependency-gated hooks (useKeyboardJs,useSpring) are intentionally omitted fromsrc/index.ts.
11. Quick Reference
| Need | Where |
|---|---|
| What is exported | src/index.ts (the only authoritative list) |
| Shared helpers | src/misc/util.ts, src/misc/hookState.ts |
| Hook factories | src/factory/ |
| SSR strategy | isBrowser/isNavigator guards + export-time impl swap (§3) |
| Build outputs | lib/ (CJS), esm/ (ESM), types in lib/index.d.ts |
| Run the demo | yarn start → Storybook :6008 |
| Run tests | yarn test (browser) + yarn test:ssr (Node) |
| Release | semantic-release on master/next |
| License | Unlicense (public domain) |
| Maintenance status | In maintenance mode; see §9 |
Key facts (from source)
- Package name/version:
react-use17.6.0; entrysrc/index.ts. - Peer deps:
react,react-dom(range*); React 16.8+ required for Hooks. - Dual build via
tsc:lib/(CJS) +esm/(ESM);"sideEffects": false. - ~100 hooks + 6 exported factories; 2 factories (
createRouter,createRenderProp) and 2 hooks (useKeyboardJs,useSpring) are intentionally not exported. - No application config, no runtime env vars, no server component.