# react-use Admin & Architecture Guide

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](#6-dependencies)) |
| 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](#9-maintenance--operational-status).

---

## 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:

1. **One hook per file.** Every public hook lives in its own `src/use*.ts(x)`
   module and is re-exported by name from `src/index.ts`. This is what makes
   tree-shaking effective (`"sideEffects": false`).
2. **Hooks compose hooks.** Higher-level hooks are built from lower-level ones —
   e.g. `useAsync` wraps `useAsyncFn`; `useCounter` wraps `useGetSet`;
   `useBoolean` is literally `useToggle`; `useNumber` is `useCounter`. The
   dependency graph is internal and acyclic.
3. **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:

```ts
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](#3-server-side-rendering-strategy).

`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.

```ts
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.

```ts
// 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:

```ts
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
> other `window`-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`:
> `createRouter` and `createRenderProp`. 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_ENV` consumer 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 by `useCookie`) is affected by a reported
> prototype-pollution advisory (community issue #2704, CVE-2026-46625). If your
> application uses `useCookie`, audit the resolved `js-cookie` version and force a
> patched release via your package manager's override/resolution mechanism, or
> avoid `useCookie` and 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 `console` diagnostics via `NODE_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](#3-server-side-rendering-strategy), 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](#72-known-dependency-vulnerability--js-cookie)). |

### 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.2` in
  devDependencies). Treat React 18/19 support as community-verified rather than
  CI-guaranteed.

---

## 10. Contributing

The repository is a standard TypeScript library workflow:

```bash
# 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/`, named `useThing.ts(x)`.
- [ ] Re-export it by name from `src/index.ts` (the public barrel).
- [ ] Guard any `window`/`navigator`/`document` access with `isBrowser` /
      `isNavigator` and provide an SSR-safe export (real-vs-mock swap).
- [ ] Reuse `on`/`off` from `src/misc/util.ts` for event listeners and clean them
      up on unmount.
- [ ] Use `resolveHookState` for 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 under `tests/`, 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 from `src/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](#3-server-side-rendering-strategy)) |
| 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](#9-maintenance--operational-status) |

### Key facts (from source)

- Package name/version: `react-use` `17.6.0`; entry `src/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.

---

_Generated June 2026 from streamich/react-use at 9ef9535. AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it. Unofficial; not affiliated with or reviewed by the project._
