Skip to content

Repository files navigation

Reactilities

React hooks for the parts you keep rewriting: storage, media queries, clicks outside, debouncing, observers, timers.

npm version CI codecov

npm install reactilities
import { useState } from 'react';
import { useLocalStorage, useDebounce, useClickOutside } from 'reactilities';

function Search() {
  const [recent, setRecent] = useLocalStorage<string[]>('recent', []);
  const [query, setQuery] = useState('');
  const [isOpen, setIsOpen] = useState(false);

  const debouncedQuery = useDebounce(query, 300);
  const panelRef = useClickOutside<HTMLDivElement>(() => setIsOpen(false));

  return (
    <div ref={panelRef}>
      <input
        value={query}
        onFocus={() => setIsOpen(true)}
        onChange={(e) => setQuery(e.target.value)}
      />
      {isOpen && (
        <Results
          query={debouncedQuery}
          recent={recent}
          onPick={(pick) => setRecent([pick, ...recent])}
        />
      )}
    </div>
  );
}

Every hook is also its own entry point, so you can pull in one without the rest: reactilities/useDebounce is 420 bytes.

Requirements

React 18 or newer, tested on 18 and 19. Several hooks are built on useSyncExternalStore, which 18 introduced.

react-dom is an optional peer dependency. No hook imports it; you need it only if you render portals yourself.

Importing

Import from the package root or from a hook's own subpath. Both tree-shake:

import { useDebounce, useLocalStorage } from 'reactilities';
import { useDebounce } from 'reactilities/useDebounce';

Next.js and Server Components

Every hook ships with the "use client" directive, so Client Components in the App Router can import them without a wrapper.

Hooks that read browser-only state (useMediaQuery, useLocalStorage, useNetworkState, useWindowSize, and others) return a stable server snapshot during SSR rather than throwing. The server render and the first client render agree, so there is no hydration mismatch; the real value applies after mount.

Conventions

Two conventions run through the library, so learning one hook teaches you most of the rest.

Hooks give you the ref; you attach it. No hook asks you to create a ref and pass it in:

const ref = useClickOutside<HTMLDivElement>(close);
const [hoverRef, isHovered] = useHover<HTMLDivElement>();
const { ref: sizeRef, width } = useResizeObserver<HTMLDivElement>();

<div ref={ref} />

These are callback refs, so React tells the hook when the element mounts, is replaced, or goes away. Conditionally rendered elements need no extra wiring. Each ref also exposes .current if you want to read the node. Use useMergedRefs to put two of them on the same element.

The exception is useEventListener, which takes its target (window, document, or an element) directly, since that target is usually not something your component renders.

Return shapes follow useState. A hook built around a single value returns a tuple, so you name the parts at the call site:

const [theme, setTheme] = useLocalStorage('theme', 'light');
const [consent, setConsent, clearConsent] = useCookie('consent');

A hook with several independent results returns an object with named fields:

const { ref, width, height } = useResizeObserver<HTMLDivElement>();
const { data, error, loading, execute } = useAsync(fetchUsers);

useHover is the one place a tuple holds two unrelated things, [ref, isHovered], because naming them at the call site reads better than hover.ref.

Types

Type inference works without annotations, and generics are there when you want to be explicit:

const [user, setUser] = useLocalStorage('user', { name: 'Ada', age: 36 });
// user: { name: string; age: number }

const { data } = useAsync<User[]>(fetchUsers);
// data: User[] | null

A few shared types are exported as well: HookRef, Nullable, Maybe, ValueOf, and DeepPartial.

Type resolution is checked with publint and are-the-types-wrong under bundler, node16, and nodenext.

API Reference

Every hook has its own page with examples and notes.

DOM Hooks

Hooks bound to a specific element or to the document.

State Hooks

Hooks that hold and manage state.

  • useBoolean - Boolean state with named setTrue/setFalse/toggle controls
  • useClipboard - Read from and write to the clipboard with hasCopied tracking
  • useControllableState - State that works controlled or uncontrolled (for reusable components)
  • useCookie - Read and write browser cookies
  • useCounter - Numeric counter with min/max bounds and step
  • useList - Manage array state with push, remove, sort, and more
  • useLocalStorage - Persist state in localStorage with sync
  • useMap - Manage Map state with React integration
  • useObjectState - Manage object state with partial updates
  • usePrevious - Access the previous render's value
  • useRafState - State updates coalesced to one per animation frame
  • useSessionStorage - Persist state in sessionStorage
  • useSet - Manage Set state with React integration
  • useStep - Multi-step wizard and onboarding flow state management
  • useUndoRedo - State with full undo/redo history

Sensor Hooks

Read-only hooks for device, browser, and viewport state.

Timing Hooks

Debouncing, throttling, intervals, timeouts, and countdowns.

Async Hooks

Async work and long-lived connections.

  • useAsync - Manage async operations with loading/error/success states
  • useAutoSave - Auto-save data after a debounce delay with pending/saved/error status
  • useEventSource - Manage Server-Sent Events (SSE) connections
  • useFileReader - Read File objects as text, data URL, or ArrayBuffer
  • useWebSocket - Manage WebSocket connections
  • useWorker - Run heavy computations in a Web Worker off the main thread

Lifecycle Hooks

Effect timing and render-lifecycle helpers.

Development Hooks

Debugging aids. Intended for development only.

  • useLogger - Log component mount, update, and unmount events (dev only)
  • useWhyDidYouRender - Debug unnecessary re-renders by logging what changed (dev only)

Helper Functions

Utility functions for common tasks.

  • classnames - Conditionally join CSS class names (aliased as cn)
  • loadScript - Dynamically load external scripts

Contributing

Bug reports and pull requests are welcome. See CONTRIBUTING.md for how to run the test suite and the documentation fixtures.

Changelog

See CHANGELOG.md, kept in the Keep a Changelog format.

Reactilities is pre-1.0 and is being tightened toward a stable API. Breaking changes are batched into as few releases as possible, and every removal ships with a replacement in MIGRATION.md.

Support

If you find this library useful and are feeling generous, consider donating to Svratište — a day center in Belgrade providing support, meals, and shelter for people experiencing homelessness.

Facebook Instagram Donate

License

MIT © Petar Basic

Releases

Packages

Contributors

Languages