Skip to content
Guides4 min readUpdated

The React Hooks Cheat Sheet

The short answer

React ships eleven commonly used built-in hooks: useState, useEffect, useContext, useReducer, useCallback, useMemo, useRef, useLayoutEffect, useId, useTransition and useSyncExternalStore. Everything else — persisting to localStorage, debouncing a value, detecting clicks outside an element, watching a media query — is a custom hook you either write yourself or install. @danixsoft/hooks provides 44 of the most common ones with zero dependencies and full TypeScript types.

Hooks replaced class components in 2019 and have barely changed since. What has changed is the set of problems people solve with them. This page is two references in one: the built-in hooks React gives you, and the custom hooks almost every application ends up needing.

Key takeaway

If you find yourself writing the same useEffect in three components, that is a custom hook trying to get out. The whole point of hooks is that stateful logic becomes portable.

The built-in React hooks

These come with React itself. You never install them, and every custom hook is ultimately built out of them.

HookWhat it gives youReach for it when
useStateA value and a setter that triggers a re-renderAny piece of state a component owns
useEffectA side effect that runs after render, with cleanupSubscriptions, timers, imperative DOM work
useContextThe nearest provider value for a contextTheme, auth, locale — anything ambient
useReducerState transitions expressed as actionsState with several fields that change together
useCallbackA memoised function identityPassing callbacks to memoised children
useMemoA memoised computed valueGenuinely expensive derivations only
useRefA mutable box that survives renders without causing oneDOM nodes, timers, "previous value" tracking
useLayoutEffectAn effect that runs before the browser paintsMeasuring layout, avoiding visual flicker
useIdA stable unique id across server and clientLinking labels to inputs in SSR apps
useTransitionA pending flag for non-urgent updatesKeeping the UI responsive during heavy renders
useSyncExternalStoreA tear-free subscription to an outside storeIntegrating non-React state libraries

The two rules that catch everyone

Hooks must be called at the top level of a component or another hook — never inside a condition, loop, or nested function — and only from React functions. React tracks hooks positionally, so a conditional hook shifts every subsequent hook by one and corrupts your state.

The custom hooks every app ends up needing

React deliberately stops at primitives. The gap between useState and a working feature is where custom hooks live. Below is that gap, grouped the way it actually shows up in a codebase.

State that has to outlive a render

Plain useState is gone the moment the component unmounts. Preferences, drafts and dismissals usually need to survive a reload — or at least a route change.

Persisted theme preference
1import { useLocalStorage } from '@danixsoft/hooks';
2
3function ThemeToggle() {
4  const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'dark');
5
6  return (
7    <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
8      {theme === 'dark' ? 'Switch to light' : 'Switch to dark'}
9    </button>
10  );
11}

Reading the browser

Viewport size, media queries, scroll position, visibility, network status. Each one is an event listener plus cleanup plus an SSR guard — three chances to get it subtly wrong.

Timing and rate limiting

A search box that fires a request on every keystroke is the single most common performance bug in React applications. Debouncing it is one line.

Debounced search
1import { useState } from 'react';
2import { useDebounce, useFetch } from '@danixsoft/hooks';
3
4function Search() {
5  const [query, setQuery] = useState('');
6  const debouncedQuery = useDebounce(query, 300);
7  const { data, loading } = useFetch(`/api/search?q=${debouncedQuery}`);
8
9  return (
10    <>
11      <input value={query} onChange={(e) => setQuery(e.target.value)} />
12      {loading ? <Spinner /> : <Results items={data} />}
13    </>
14  );
15}
Note which value goes where: the input stays bound to query so typing feels instant, while the request uses debouncedQuery. Swapping them makes the field feel laggy.

Interaction and gestures

Lifecycle escape hatches

These exist because useEffect alone cannot express "only on updates", "only on unmount", or "always the latest closure" without boilerplate that is easy to get wrong.

The five mistakes that account for most hook bugs

  1. 1

    Stale closures in intervals and event handlers

    A callback registered once captures the state from that render forever. useInterval and useEvent both solve this by keeping the callback in a ref and always invoking the latest version.

  2. 2

    Missing cleanup

    Every subscription, timer and listener you create in an effect must be returned as a cleanup function. Forgetting this leaks memory and, in React Strict Mode, doubles your side effects in development.

  3. 3

    Objects and arrays in dependency arrays

    A fresh object literal is a new identity on every render, so the effect runs every time. Depend on primitive fields, or memoise the object with useMemo.

  4. 4

    Reaching for useMemo too early

    Memoisation is not free — it costs a comparison and extra memory on every render. Measure before you memoise anything cheaper than a few milliseconds.

  5. 5

    Touching window during server rendering

    Any hook that reads window, document, localStorage or navigator must guard for the server, or your Next.js build will crash and your hydration will mismatch.

The SSR problem is big enough to deserve its own page — see SSR-safe hooks in Next.js for the full treatment.

Install the whole set

Every hook linked on this page is a named export from one tree-shakeable package.

npm install @danixsoft/hooks
How many hooks does React have built in?

React 19 exposes around a dozen hooks in common use: useState, useEffect, useContext, useReducer, useCallback, useMemo, useRef, useImperativeHandle, useLayoutEffect, useDebugValue, useId, useDeferredValue, useTransition, useSyncExternalStore and useActionState. Most applications use five or six of them regularly.

Should I write my own hooks or install a library?

Write your own for logic specific to your product — that is the whole point of custom hooks. Install a library for the generic, well-understood problems (debouncing, localStorage, media queries) where a well-tested implementation already exists and the edge cases are known.

Do custom hooks hurt performance?

No. A custom hook is a plain function call; it adds no component to the tree and no extra render. Performance depends on what the hook does internally, not on the fact that it is a hook.

Can a custom hook call another custom hook?

Yes, and that is normal. Hooks compose freely as long as every call happens at the top level of the function. Several hooks in this library are built on top of others in it.

Keep reading