Skip to main content

React Hooks Cheatsheet — All 17 Built-In Hooks Plus Custom Patterns, with Real Examples and Pitfalls

React hooks cheat sheet — all 17 built-in hooks (useState / useEffect / useMemo / useTransition / useFormStatus...) with real examples and pitfalls.

  • Runs locally
  • Category Developer & DevOps
  • Best for Formatting, validating, shrinking, or inspecting code-adjacent text.
46 hooks
Built-in (18)

useState

Built-in
Signatureconst [state, setState] = useState<T>(initial: T | (() => T))

The bread-and-butter state hook. Returns a tuple of [current value, setter]. The setter is stable across renders; calling it triggers a re-render with the new value.

When to use

Any per-component value that needs to trigger a re-render when it changes — form fields, toggles, counters, fetched data, UI mode.

When to avoid

Derived values (compute them inline or with useMemo instead). Refs that should NOT trigger re-render (use useRef). Complex state with many transitions (use useReducer).

Common pitfall: Stale closure: if you call setState(count + 1) twice in a row, only one increment happens because both reads of `count` capture the same value. Always use the functional form setState(c => c + 1) when the next value depends on the previous.

Examples
const [count, setCount] = useState(0);
// functional form is mandatory when next depends on prev
const inc = () => setCount(c => c + 1);
// lazy initializer — fn runs once, not on every render
const [grid, setGrid] = useState(() => buildExpensiveGrid(1000));

useReducer

Built-in
Signatureconst [state, dispatch] = useReducer<R>(reducer, initialArg, init?)

State hook for state shapes with multiple transitions. The reducer is a (state, action) => state function. dispatch is stable, so it is safe to pass to children without breaking memoization.

When to use

Form state with 5+ fields. Async flows with idle/loading/success/error transitions. Anywhere you find yourself writing several related setState calls in one handler.

When to avoid

Single boolean or counter (useState is shorter). State that lives in only one component and never transitions through more than two values (useState wins on line count).

Common pitfall: The reducer must be pure — no fetch, no setTimeout, no DOM access inside. Side effects belong in useEffect or the handler that calls dispatch.

Examples
type Action = { type: 'inc' } | { type: 'reset' };
function reducer(state: number, action: Action) {
  switch (action.type) {
    case 'inc': return state + 1;
    case 'reset': return 0;
  }
}
const [count, dispatch] = useReducer(reducer, 0);
// async flow modeled as a state machine
type S = { status: 'idle' | 'loading' | 'ok' | 'err'; data?: User };
type A = { type: 'start' } | { type: 'ok'; data: User } | { type: 'err' };

useEffect

Built-in
SignatureuseEffect(effect: () => (void | (() => void)), deps?: any[])

Run side effects AFTER the browser paints. The optional cleanup function runs before the next effect and on unmount. Deps array controls when the effect re-fires.

When to use

Subscribing to external systems (WebSocket, event listener, observer). Sync with non-React state (localStorage, document.title). Anything that should react to a prop / state change AFTER the DOM updates.

When to avoid

Deriving state from props (just compute it inline). Running on every render with no cleanup (you do not need useEffect for that — it is just code in the body). Resetting state when a prop changes (use `key` prop or compute during render with a previous-value check).

Common pitfall: Missing deps = stale closure: the effect captures the values from the render where it was DEFINED. Lint with `eslint-plugin-react-hooks` exhaustive-deps and either include every value you read, or move the value into a ref or out of the component.

Examples
useEffect(() => {
  const id = setInterval(() => setTick(t => t + 1), 1000);
  return () => clearInterval(id);   // cleanup is mandatory for timers
}, []);
// subscribe + cleanup pattern
useEffect(() => {
  const onResize = () => setWidth(window.innerWidth);
  window.addEventListener('resize', onResize);
  return () => window.removeEventListener('resize', onResize);
}, []);
// data fetching with abort to avoid setState-after-unmount warning
useEffect(() => {
  const ctrl = new AbortController();
  fetch(`/api/user/${id}`, { signal: ctrl.signal })
    .then(r => r.json()).then(setUser).catch(() => {});
  return () => ctrl.abort();
}, [id]);

useLayoutEffect

Built-in
SignatureuseLayoutEffect(effect: () => (void | (() => void)), deps?: any[])

Same signature as useEffect but fires SYNCHRONOUSLY after the DOM mutation and BEFORE the browser paints. Use to measure or mutate layout without a flash.

When to use

Measuring DOM size and using it to position something in the same paint. Reading scroll position. Synchronously fixing layout before the user sees the flash of the wrong state.

When to avoid

Anything that does not need to be visually consistent on the same paint. Heavy work (you block paint and tank perf). Server rendering — useLayoutEffect does not run on the server and React warns.

Common pitfall: It blocks paint. Long work here = jank. If you see "useLayoutEffect does nothing on the server" warnings during SSR, switch to useEffect or guard the import.

Examples
useLayoutEffect(() => {
  const { height } = ref.current!.getBoundingClientRect();
  setRowHeight(height);   // setState is safe — it batches before paint
}, []);

useInsertionEffect

Built-in
SignatureuseInsertionEffect(effect: () => (void | (() => void)), deps?: any[])

Niche hook designed for CSS-in-JS libraries. Fires synchronously BEFORE any DOM mutations so libraries can inject <style> tags without causing layout to thrash.

When to use

Almost never in app code. The intended consumers are styled-components / Emotion / Stitches authors who need to insert generated stylesheets before React commits.

When to avoid

You almost certainly want useEffect or useLayoutEffect. Reading layout here gives wrong values — the DOM has not been mutated yet. No refs are attached when this fires.

Common pitfall: Refs are not yet attached and you cannot use setState here — those are documented React contract. Treat this as a library-only hook.

Examples
// styled-components-style use case
useInsertionEffect(() => {
  const tag = document.createElement('style');
  tag.textContent = generatedCss;
  document.head.appendChild(tag);
  return () => tag.remove();
}, [generatedCss]);

useMemo

Built-in
Signatureconst value = useMemo<T>(() => compute(), deps)

Cache the return value of an expensive computation between renders. The function runs only when one of the deps changes.

When to use

Expensive pure computation (sort + filter of a long list, heavy parsing). Stabilizing a referenced object/array passed to memoized children or used as another hook deps.

When to avoid

Cheap computations — useMemo itself has overhead. Anywhere you would only "feel safer" with it. Wrapping every value is a known anti-pattern that bloats the bundle and slows things down.

Common pitfall: useMemo is a hint, not a guarantee — React may discard the cached value to free memory. Never put logic that MUST run only once inside; use useEffect with [] or a ref instead.

Examples
const visible = useMemo(
  () => items.filter(i => i.name.includes(query)).sort(byDate),
  [items, query],
);
// stabilize an object so memoized child does not re-render
const style = useMemo(() => ({ color, fontSize }), [color, fontSize]);

useCallback

Built-in
Signatureconst fn = useCallback<T>(callback, deps)

Memoize a function reference between renders so that React.memo children or downstream hooks see a stable callback. Equivalent to useMemo(() => fn, deps).

When to use

Passing a handler to a memoized child (React.memo / PureComponent). Including a function in another hook deps array. Otherwise: skip it.

When to avoid

Non-memoized children do not benefit (they re-render anyway). Wrapping every handler is the same anti-pattern as wrapping every value in useMemo.

Common pitfall: Same closure trap as useEffect: missing a dep means the cached function reads STALE values. exhaustive-deps lint is mandatory.

Examples
const onSelect = useCallback(
  (id: string) => setSelected(s => ({ ...s, [id]: true })),
  [],   // setSelected from useState is already stable
);

useRef

Built-in
Signatureconst ref = useRef<T>(initial: T): { current: T }

Returns a mutable .current object that survives re-renders WITHOUT triggering them. Two uses: (1) DOM node reference via the ref attribute, (2) mutable instance value (like a class field).

When to use

Read or focus a DOM node. Store a timer id, previous value, or instance flag that must not cause a re-render. Hold a mutable cache.

When to avoid

Anything the UI must react to — that needs useState. Refs are skipped during reconciliation, so mutating .current never re-renders.

Common pitfall: Reading ref.current during render is fragile — for DOM refs the value is null on first render. Read in useEffect / useLayoutEffect or event handlers, not the render body.

Examples
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => { inputRef.current?.focus(); }, []);
return <input ref={inputRef} />;
// instance value pattern — no re-render on change
const lastSeenRef = useRef(0);
function onScroll() { lastSeenRef.current = window.scrollY; }

useImperativeHandle

Built-in
SignatureuseImperativeHandle<T, R extends T>(ref, () => R, deps?)

Customize the value exposed to a parent via ref. Pair with forwardRef. Use to expose a narrow imperative API like { focus, scrollIntoView } instead of the raw DOM node.

When to use

A reusable input component that exposes focus() and clear() to parents. A modal that exposes open()/close(). Any wrapped component where the parent needs a small imperative API.

When to avoid

Anything that could be expressed declaratively via props — prefer that. Exposing the whole DOM node — just forward the ref directly.

Common pitfall: Without forwardRef, ref does nothing on a function component. Forgetting the deps array means the imperative API is re-created every render.

Examples
const Input = forwardRef<{ focus: () => void }, Props>((props, ref) => {
  const inner = useRef<HTMLInputElement>(null);
  useImperativeHandle(ref, () => ({
    focus: () => inner.current?.focus(),
  }), []);
  return <input ref={inner} {...props} />;
});

useContext

Built-in
Signatureconst value = useContext<T>(Context)

Read the value of a Context inside a function component. The component re-renders whenever the nearest Provider above it pushes a new value.

When to use

Cross-cutting concerns: theme, current user, i18n, feature flags. Any value many components in a subtree need without prop-drilling.

When to avoid

Frequently-changing values (every consumer re-renders). Replacement for a real state library when you need fine-grained updates — use Zustand / Jotai / Redux selectors instead.

Common pitfall: Putting an inline object as Provider value re-creates it every render and re-renders every consumer. Memoize the value with useMemo: <Ctx.Provider value={useMemo(() => ({a, b}), [a, b])}>.

Examples
const ThemeCtx = createContext<'light' | 'dark'>('light');
function Toolbar() {
  const theme = useContext(ThemeCtx);
  return <div className={theme}>...</div>;
}

useTransition

Built-in
Signatureconst [isPending, startTransition] = useTransition()

Mark a state update as non-urgent. React keeps the previous UI interactive while the transition renders in the background, and isPending tells you it is running.

When to use

Filtering a long list while typing. Tab switches where the new tab is expensive to render. Anywhere a slow render makes typing or clicking feel sluggish.

When to avoid

Urgent updates that the user expects to be immediate (input value, button toggle). Side effects — startTransition is for state updates, not for fetch/timer.

Common pitfall: Wrapping a fetch call does nothing — only the state UPDATES inside the callback are deferred. Use Suspense + use() for data, useTransition for the resulting state.

Examples
const [isPending, startTransition] = useTransition();
function onChange(e: ChangeEvent<HTMLInputElement>) {
  setQuery(e.target.value);                 // urgent: input updates now
  startTransition(() => setList(filter(e.target.value))); // non-urgent
}

useDeferredValue

Built-in
Signatureconst deferred = useDeferredValue<T>(value)

Return a deferred copy of a value. The deferred copy lags behind the real one during expensive renders, letting urgent updates win. Like useTransition but for values you do not own (props).

When to use

You receive a frequently-changing prop and want to render a heavy view from it without blocking typing. Pair with React.memo on the heavy child so the deferred prop only triggers the slow render.

When to avoid

You own the setter — useTransition is more explicit. Tiny values where deferring causes flicker without performance benefit.

Common pitfall: Without React.memo on the heavy consumer, useDeferredValue still triggers the slow render — just slightly later. The memo is what makes it work.

Examples
function Search({ query }: { query: string }) {
  const deferred = useDeferredValue(query);
  const isStale = deferred !== query;
  return (
    <div style={{ opacity: isStale ? 0.5 : 1 }}>
      <HeavyList query={deferred} />
    </div>
  );
}

useSyncExternalStore

Built-in
Signatureconst snapshot = useSyncExternalStore<T>(subscribe, getSnapshot, getServerSnapshot?)

Subscribe to an external (non-React) data source — Redux store, browser API, observable — without tearing under concurrent rendering. Returns the current snapshot and re-renders when the store notifies.

When to use

Writing a custom hook over a non-React store (window.matchMedia, document.visibilityState, a third-party state manager). Library authors making concurrent-safe bindings.

When to avoid

Plain component state (useState) or a context-based store (useContext). Most app code never needs this directly — it is plumbing.

Common pitfall: getSnapshot MUST return a referentially stable value when nothing changed — returning a fresh object every call causes an infinite render loop. Cache snapshots in a closure.

Examples
function useOnline() {
  return useSyncExternalStore(
    (cb) => {
      window.addEventListener('online', cb);
      window.addEventListener('offline', cb);
      return () => {
        window.removeEventListener('online', cb);
        window.removeEventListener('offline', cb);
      };
    },
    () => navigator.onLine,
    () => true,  // SSR snapshot
  );
}

useId

Built-in
Signatureconst id = useId(): string

Generate a unique, deterministic id that is stable across server and client renders. Designed for accessibility attributes that need to associate a label with an input.

When to use

aria-labelledby, htmlFor + id pairs in a reusable form component, aria-describedby on tooltips. Anywhere you need a unique id that survives SSR.

When to avoid

Keys in a list (use the data id). CSS selectors or DOM lookup ids — the returned string contains : and is not a valid CSS identifier without escaping.

Common pitfall: The id contains colons (e.g. `:r1:`) — fine in HTML attributes but breaks naive querySelector(`#${id}`) calls. Use document.getElementById, which accepts any string.

Examples
function Field({ label }: { label: string }) {
  const id = useId();
  return <><label htmlFor={id}>{label}</label><input id={id} /></>;
}

useOptimistic

Built-in
Signatureconst [optimistic, addOptimistic] = useOptimistic<T, A>(state, updateFn)

React 19+. Show an immediate, optimistic UI while a server action is in flight. Returns a temporary state computed from the real state plus pending optimistic updates; reconciles when the action resolves.

When to use

Like / upvote button. Sending a chat message. Toggling a setting where the server confirmation is fast but you want zero latency on the UI.

When to avoid

Long server roundtrips where failure is common — the rollback flash is worse than just showing a spinner. Cases where the optimistic value would be wrong (server normalizes input).

Common pitfall: Must be called inside a transition or a server action — calling it from a normal event handler does nothing visible. The optimistic value disappears as soon as the action resolves; sync to local state if you need to keep it.

Examples
const [optimisticLikes, addLike] = useOptimistic(
  likes,
  (current, _: number) => current + 1,
);
async function onLike() {
  addLike(1);                   // UI updates immediately
  await serverLike(postId);     // real call
}

useActionState

Built-in
Signatureconst [state, action, isPending] = useActionState<S, P>(reducerFn, initial, permalink?)

React 19+. Manage state that transitions via an async action — typically a form submit. Combines a reducer-style update, automatic pending flag, and progressive enhancement with form actions.

When to use

Form submission with server validation (e.g. login form returning {error: "wrong password"}). Any async transition where you want pending state without writing useState + try/finally by hand.

When to avoid

Synchronous state changes (useState / useReducer is simpler). Non-form-like flows — useTransition gives more flexibility.

Common pitfall: The reducer signature is (state, formData) => Promise<state>, NOT the classic (state, action). isPending is true even if the user navigates away mid-action — clean up in an effect if it matters.

Examples
async function login(prev: State, fd: FormData): Promise<State> {
  const res = await api.login(fd.get('email'), fd.get('password'));
  return res.ok ? { user: res.user } : { error: res.message };
}
const [state, action, isPending] = useActionState(login, { user: null });
return <form action={action}>...</form>;

useFormStatus

Built-in
Signatureconst { pending, data, method, action } = useFormStatus()

React 19+. Read the submission status of the PARENT <form>. Designed to be called from a button/input INSIDE a form so it can disable itself or show a spinner without prop-drilling.

When to use

A reusable <SubmitButton> that disables itself while the form is submitting. Field-level inline progress indicators that do not need to know the form's implementation.

When to avoid

Outside of a form (returns { pending: false, data: null }). When you already have isPending from useActionState — just thread that through.

Common pitfall: Returns false / null if the component is NOT a descendant of the form. Calling it on the same component as the <form> always returns false — it reads the PARENT form, not the current one.

Examples
function SubmitButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending}>
    {pending ? 'Saving…' : 'Save'}
  </button>;
}

use

Built-in
Signatureconst value = use<T>(promise | Context)

React 19+. The unified read primitive — pass a Promise to suspend until it resolves, or a Context to read its value. Unlike other hooks, use() can be called inside if/loop/early-return.

When to use

Reading a server-action Promise in a Suspense boundary (the canonical way to render async data in React 19). Conditional context reads (useContext cannot be called conditionally).

When to avoid

Creating a fresh Promise inside the component body — every render creates a new Promise and you suspend forever. Pass the Promise in from a parent or memoize it.

Common pitfall: use(somePromise) reads CACHED promises. If you do not stabilize the Promise (in a parent, via cache(), or React Query), you cause an infinite suspend-resolve-render loop.

Examples
function User({ userPromise }: { userPromise: Promise<User> }) {
  const user = use(userPromise);   // suspends until resolved
  return <div>{user.name}</div>;
}

// conditional context read — useContext cannot do this
const value = condition ? use(CtxA) : use(CtxB);
Custom (28)

useToggle

Custom
Signatureconst [on, toggle, setOn] = useToggle(initial = false)

Three-element tuple for boolean state: current value, a stable toggle function, and the raw setter. The toggle ignores arguments so it can be passed directly as onClick.

When to use

Disclosure / accordion open state. Modal open/close. Light/dark toggle. Anywhere you write `setOpen(o => !o)` more than once.

When to avoid

Non-boolean state. Booleans that need optimistic / async transitions — use useOptimistic or useActionState.

Common pitfall: If you pass toggle to onClick and TypeScript complains about MouseEvent vs void — overload the signature so it ignores its argument: () => void.

Examples
function useToggle(initial = false) {
  const [on, setOn] = useState(initial);
  const toggle = useCallback(() => setOn(o => !o), []);
  return [on, toggle, setOn] as const;
}
const [open, toggleOpen] = useToggle();
<button onClick={toggleOpen}>{open ? 'Hide' : 'Show'}</button>

useLocalStorage

Custom
Signatureconst [value, setValue] = useLocalStorage<T>(key, initial)

Same shape as useState but persists to window.localStorage and rehydrates on mount. Should serialize via JSON and guard against SSR (window undefined).

When to use

User preferences (theme, sidebar collapsed). Form drafts. Anything that should survive a page reload but does not belong on the server.

When to avoid

Sensitive data (everyone can read localStorage in DevTools). Large blobs (5 MB cap per origin and synchronous I/O). Cross-tab sync without listening to the `storage` event.

Common pitfall: JSON.parse throws on bad data — wrap in try/catch and fall back to initial. typeof window === "undefined" check is mandatory in Next.js / Astro SSR or the build crashes.

Examples
function useLocalStorage<T>(key: string, initial: T) {
  const [v, setV] = useState<T>(() => {
    if (typeof window === 'undefined') return initial;
    try { const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : initial; }
    catch { return initial; }
  });
  useEffect(() => { localStorage.setItem(key, JSON.stringify(v)); }, [key, v]);
  return [v, setV] as const;
}

useDebounce

Custom
Signatureconst debounced = useDebounce<T>(value, delayMs)

Returns a value that lags behind the input by delayMs of "quiet time" — every change resets the timer. Use to throttle expensive work driven by frequently-changing values.

When to use

Search-as-you-type before firing an API call. Auto-save drafts. Window resize → recompute layout.

When to avoid

Each individual update matters (controlled input value — the input itself should NOT be debounced, only the derived effect). Cases where useDeferredValue gives you the same UX with concurrent rendering.

Common pitfall: Returning a stale value on dependency change — make sure the cleanup clears the timer. Debounce delay too short is a no-op; too long feels broken. 200-500 ms is the sweet spot for search.

Examples
function useDebounce<T>(value: T, delay = 300): T {
  const [v, setV] = useState(value);
  useEffect(() => {
    const id = setTimeout(() => setV(value), delay);
    return () => clearTimeout(id);
  }, [value, delay]);
  return v;
}

useFetch

Custom
Signatureconst { data, error, loading } = useFetch<T>(url, options?)

Toy hook for one-off GET requests. Returns { data, error, loading } and aborts in-flight requests when url or component unmounts. NOT a replacement for React Query / SWR — those handle cache, retry, dedupe.

When to use

Prototypes, demos, internal tools where a real data library is overkill. Reading a single endpoint that does not need cache.

When to avoid

Production apps. Endpoints that are read in multiple places (you will dedupe by hand). Anywhere caching, retry, or mutation matters.

Common pitfall: Without AbortController, a fast-changing url leaves you with race conditions where an OLDER response overwrites a newer one. Always abort in the cleanup.

Examples
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    const ctrl = new AbortController();
    setLoading(true);
    fetch(url, { signal: ctrl.signal })
      .then(r => r.json()).then(d => { setData(d); setLoading(false); })
      .catch(e => { if (e.name !== 'AbortError') { setError(e); setLoading(false); } });
    return () => ctrl.abort();
  }, [url]);
  return { data, error, loading };
}

useMediaQuery

Custom
Signatureconst matches = useMediaQuery(query: string): boolean

Return whether a CSS media query currently matches. Subscribes via window.matchMedia and re-renders on change. Pair with useSyncExternalStore for concurrent safety.

When to use

Render different components on mobile vs desktop. Toggle layouts at a breakpoint. Detect dark-mode preference via (prefers-color-scheme: dark).

When to avoid

Pure styling — CSS media queries are cheaper than re-rendering React. Layout decisions that should be measured (use ResizeObserver instead).

Common pitfall: SSR returns false because window is undefined — that causes a layout flash. Wrap in useSyncExternalStore with a sane server snapshot, or render only on the client.

Examples
function useMediaQuery(query: string) {
  const subscribe = (cb: () => void) => {
    const mql = window.matchMedia(query);
    mql.addEventListener('change', cb);
    return () => mql.removeEventListener('change', cb);
  };
  return useSyncExternalStore(
    subscribe,
    () => window.matchMedia(query).matches,
    () => false,
  );
}
const isMobile = useMediaQuery('(max-width: 768px)');

useEventListener

Custom
SignatureuseEventListener<K>(event: K, handler: (e) => void, target?, options?)

Wrapper around addEventListener that pairs the listener with the React lifecycle and uses a ref to keep the handler fresh without re-subscribing on every render.

When to use

Keyboard shortcuts on window. scroll / resize / online-offline. Listening to a third-party element ref. Anywhere you would otherwise hand-roll useEffect + addEventListener + cleanup.

When to avoid

React-managed elements — just use onClick / onKeyDown props. Heavy events (mousemove on document) without throttling.

Common pitfall: Without the handler-in-ref trick, every render re-subscribes and the listener cost balloons. Capture deps explicitly and ref the latest handler.

Examples
function useEventListener<K extends keyof WindowEventMap>(
  event: K,
  handler: (e: WindowEventMap[K]) => void,
) {
  const ref = useRef(handler);
  useEffect(() => { ref.current = handler; }, [handler]);
  useEffect(() => {
    const fn = (e: WindowEventMap[K]) => ref.current(e);
    window.addEventListener(event, fn);
    return () => window.removeEventListener(event, fn);
  }, [event]);
}

useOnClickOutside

Custom
SignatureuseOnClickOutside(ref: RefObject<HTMLElement>, handler: (e) => void)

Run handler when a mousedown / touchstart fires OUTSIDE the referenced element. Canonical pattern for closing popovers, dropdowns, modals.

When to use

Click-outside-to-close on a custom dropdown, popover, mention picker, tooltip.

When to avoid

Native <dialog> elements (browsers handle Escape and outside-click for free). Components inside Portals — the ref-contains check fails because the portal child is outside the ref tree.

Common pitfall: click event fires too late (the click target may already be removed). Use mousedown / pointerdown / touchstart so the close happens BEFORE the next click handler runs.

Examples
function useOnClickOutside(
  ref: RefObject<HTMLElement>,
  handler: (e: MouseEvent | TouchEvent) => void,
) {
  useEffect(() => {
    const fn = (e: MouseEvent | TouchEvent) => {
      const el = ref.current;
      if (!el || el.contains(e.target as Node)) return;
      handler(e);
    };
    document.addEventListener('mousedown', fn);
    document.addEventListener('touchstart', fn);
    return () => {
      document.removeEventListener('mousedown', fn);
      document.removeEventListener('touchstart', fn);
    };
  }, [ref, handler]);
}

usePrevious

Custom
Signatureconst previous = usePrevious<T>(value): T | undefined

Return the value from the PREVIOUS render. Implemented with a ref written in useEffect so the read during render returns the value from one render ago.

When to use

Animating a transition between old and new value. Detecting a transition like "was open, now closed". Comparison with the last prop without holding redundant state.

When to avoid

When you actually need to RENDER both values — useState or useReducer. When you need the previous of derived data — derive both and compare directly.

Common pitfall: Because the ref is set in useEffect AFTER render, on the very first render it returns undefined. Code must handle the undefined case explicitly.

Examples
function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T | undefined>(undefined);
  useEffect(() => { ref.current = value; }, [value]);
  return ref.current;
}
const prevCount = usePrevious(count);
const direction = prevCount === undefined ? 'init' : count > prevCount ? 'up' : 'down';

useWindowSize

Custom
Signatureconst { width, height } = useWindowSize(): { width: number; height: number }

Return live window dimensions. Subscribes to resize and updates on change. SSR-safe variant uses useSyncExternalStore with a server snapshot.

When to use

Virtualized lists that need a viewport height. Canvas / SVG that draws to window dimensions. Conditional rendering above a breakpoint when CSS alone is not enough.

When to avoid

Pure CSS responsive design — media queries are cheaper. Element-level sizing (use ResizeObserver, not window resize).

Common pitfall: resize fires at 60+ Hz on some platforms. Without throttling, the entire subtree re-renders on every pixel of drag. Throttle to 16-100 ms or use rAF batching.

Examples
function useWindowSize() {
  const [size, setSize] = useState(() => ({
    width: typeof window === 'undefined' ? 0 : window.innerWidth,
    height: typeof window === 'undefined' ? 0 : window.innerHeight,
  }));
  useEffect(() => {
    let raf = 0;
    const onResize = () => {
      cancelAnimationFrame(raf);
      raf = requestAnimationFrame(() => {
        setSize({ width: window.innerWidth, height: window.innerHeight });
      });
    };
    window.addEventListener('resize', onResize);
    return () => { cancelAnimationFrame(raf); window.removeEventListener('resize', onResize); };
  }, []);
  return size;
}

useInterval

Custom
SignatureuseInterval(callback: () => void, delayMs: number | null)

Declarative setInterval. Pass null as delay to pause. Uses a ref to always call the LATEST callback closure without re-creating the interval on every render.

When to use

Polling status. Clock tick. Animation frame fallback. Any repeating action that should pause cleanly via state.

When to avoid

Smooth animation (use requestAnimationFrame for vsync). One-shot timers (just useEffect + setTimeout). Sub-millisecond precision (browsers throttle to 4 ms minimum).

Common pitfall: A naive `useEffect(() => { setInterval(cb, d); }, [cb])` re-creates the interval whenever cb identity changes — usually every render. The ref-the-callback pattern is the fix Dan Abramov's blog made famous.

Examples
function useInterval(cb: () => void, delay: number | null) {
  const ref = useRef(cb);
  useEffect(() => { ref.current = cb; }, [cb]);
  useEffect(() => {
    if (delay === null) return;
    const id = setInterval(() => ref.current(), delay);
    return () => clearInterval(id);
  }, [delay]);
}
useInterval(() => setNow(Date.now()), running ? 1000 : null);

useTimeout

Custom
SignatureuseTimeout(callback: () => void, delayMs: number | null)

Declarative setTimeout. Fires the callback once after delayMs; pass null to cancel. Like useInterval, it refs the latest callback so changing it never re-arms the timer.

When to use

Auto-dismiss a toast after a few seconds. Delay showing a loading spinner. Fire a one-shot action that should be cancellable when a prop turns it off.

When to avoid

Repeating actions (use useInterval). Animation timing (use requestAnimationFrame). Cases where a plain useEffect + setTimeout in one place is already clear enough.

Common pitfall: Like useInterval, putting the callback in the deps re-arms the timer on every render and the timeout never fires. Ref the callback and depend only on delay.

Examples
function useTimeout(cb: () => void, delay: number | null) {
  const ref = useRef(cb);
  useEffect(() => { ref.current = cb; }, [cb]);
  useEffect(() => {
    if (delay === null) return;
    const id = setTimeout(() => ref.current(), delay);
    return () => clearTimeout(id);
  }, [delay]);
}
useTimeout(() => setVisible(false), visible ? 4000 : null);

useIsMounted

Custom
Signatureconst isMounted = useIsMounted(): () => boolean

Return a getter that reports whether the component is still mounted. Useful before calling setState at the end of an async task to avoid the legacy "update on unmounted component" warning.

When to use

Guarding setState after an await in code you cannot easily refactor to AbortController. Third-party callbacks that resolve after unmount.

When to avoid

Fetch flows — prefer AbortController, which cancels the request itself. Treating it as the default pattern: in React 18+ the unmount warning was removed, so most code does not need this.

Common pitfall: Returning a boolean snapshot instead of a getter is wrong — the value is captured at render time and goes stale. Return a function that reads the ref at call time.

Examples
function useIsMounted() {
  const mounted = useRef(false);
  useEffect(() => {
    mounted.current = true;
    return () => { mounted.current = false; };
  }, []);
  return useCallback(() => mounted.current, []);
}
const isMounted = useIsMounted();
await save(); if (isMounted()) setDone(true);

useUpdateEffect

Custom
SignatureuseUpdateEffect(effect: () => (void | (() => void)), deps?: any[])

Like useEffect but skips the FIRST run (mount). Runs only on subsequent dependency updates. Implemented with a ref flag that flips after the initial render.

When to use

React to a value CHANGING without firing on mount — e.g. sync a query param only after the user edits a filter, or animate only on updates.

When to avoid

When you actually want the mount run too (plain useEffect). When the "skip first" logic hides a real design smell — sometimes the effect just should not depend on that value.

Common pitfall: In React 18 StrictMode dev, effects run twice on mount — a naive boolean flag can mis-skip. Use a ref initialized to false and set it true at the end of the effect, not a module-level flag.

Examples
function useUpdateEffect(effect: () => void | (() => void), deps?: any[]) {
  const isFirst = useRef(true);
  useEffect(() => {
    if (isFirst.current) { isFirst.current = false; return; }
    return effect();
  }, deps);
}
useUpdateEffect(() => { syncUrl(filter); }, [filter]);

useSet

Custom
Signatureconst [set, { add, remove, toggle, has, reset }] = useSet<T>(initial?)

Manage a Set in state with stable helper actions. Each action returns a NEW Set (immutability) so React detects the change. Common for multi-select / checkbox lists.

When to use

Selected row ids in a table. Expanded tree node ids. Any "membership" state where you ask "is this id selected?" a lot.

When to avoid

A single boolean (useState). Ordered data where insertion order or duplicates matter (use an array). Large sets updated per-frame (re-creating the Set each change is O(n)).

Common pitfall: Mutating the Set in place (set.add(x); setSet(set)) does NOT re-render — the reference is unchanged. Always build a new Set: setSet(s => new Set(s).add(x)).

Examples
function useSet<T>(initial?: Iterable<T>) {
  const [set, setSet] = useState(() => new Set(initial));
  const actions = useMemo(() => ({
    add: (v: T) => setSet(s => new Set(s).add(v)),
    remove: (v: T) => setSet(s => { const n = new Set(s); n.delete(v); return n; }),
    toggle: (v: T) => setSet(s => { const n = new Set(s); n.has(v) ? n.delete(v) : n.add(v); return n; }),
    reset: () => setSet(new Set()),
  }), []);
  return [set, actions] as const;
}

useCounter

Custom
Signatureconst [count, { inc, dec, set, reset }] = useCounter(initial = 0, opts?)

Numeric state with stable inc/dec/set/reset actions and optional min/max clamping. All updates use the functional setState form so rapid clicks never drop increments.

When to use

Quantity steppers, pagination page number, rating widgets — anywhere bounded integer state has a few fixed operations.

When to avoid

Free-form numeric input (just useState + parse on change). Non-integer or unit-bearing values where clamping logic is more involved.

Common pitfall: Clamping must happen inside the updater, not before it: inc should be setCount(c => clamp(c + 1)). Clamping the captured count drops concurrent updates.

Examples
function useCounter(initial = 0, { min = -Infinity, max = Infinity } = {}) {
  const clamp = (n: number) => Math.min(max, Math.max(min, n));
  const [count, setCount] = useState(clamp(initial));
  const actions = useMemo(() => ({
    inc: (step = 1) => setCount(c => clamp(c + step)),
    dec: (step = 1) => setCount(c => clamp(c - step)),
    set: (n: number) => setCount(clamp(n)),
    reset: () => setCount(clamp(initial)),
  }), []);
  return [count, actions] as const;
}

useClipboard

Custom
Signatureconst { copy, copied } = useClipboard(resetMs = 1500)

Copy text to the clipboard and expose a `copied` flag that flips true briefly. Wraps the async navigator.clipboard.writeText API and resets the flag with a timer.

When to use

Copy buttons for code snippets, share links, API keys. Anywhere you want a "Copied!" confirmation that auto-clears.

When to avoid

Copying rich content / images (Clipboard API needs ClipboardItem, not writeText). Non-secure contexts — navigator.clipboard is undefined on http:// and old browsers.

Common pitfall: navigator.clipboard.writeText returns a Promise that REJECTS without a user gesture or on permission denial. Always .catch it, and feature-detect before calling.

Examples
function useClipboard(resetMs = 1500) {
  const [copied, setCopied] = useState(false);
  const copy = useCallback(async (text: string) => {
    try {
      await navigator.clipboard.writeText(text);
      setCopied(true);
      setTimeout(() => setCopied(false), resetMs);
    } catch { setCopied(false); }
  }, [resetMs]);
  return { copy, copied };
}

useHover

Custom
Signatureconst [ref, hovered] = useHover<T extends HTMLElement>()

Track whether the pointer is over an element. Returns a ref to attach and a boolean. Uses mouseenter / mouseleave so it does not fire for child bubbling like mouseover would.

When to use

Show a toolbar or delete button on row hover. Lazy-load a preview on hover. Cases where CSS :hover cannot reach into React state.

When to avoid

Pure visual hover effects (CSS :hover is far cheaper). Touch-only UIs — there is no hover on touch, so design a tap alternative.

Common pitfall: Re-attaching listeners when the ref node changes needs a ref callback, not a static ref object. Using mouseover instead of mouseenter makes hovered flicker as the pointer crosses children.

Examples
function useHover<T extends HTMLElement>() {
  const [hovered, setHovered] = useState(false);
  const ref = useRef<T | null>(null);
  useEffect(() => {
    const node = ref.current;
    if (!node) return;
    const on = () => setHovered(true);
    const off = () => setHovered(false);
    node.addEventListener('mouseenter', on);
    node.addEventListener('mouseleave', off);
    return () => {
      node.removeEventListener('mouseenter', on);
      node.removeEventListener('mouseleave', off);
    };
  }, []);
  return [ref, hovered] as const;
}

useIntersectionObserver

Custom
Signatureconst [ref, entry] = useIntersectionObserver(options?: IntersectionObserverInit)

Observe when an element enters or leaves the viewport using IntersectionObserver. Returns a ref and the latest IntersectionObserverEntry (entry.isIntersecting tells you visibility).

When to use

Lazy-loading images / components on scroll. Infinite scroll sentinel. Fire-once animations when a section scrolls into view. Impression tracking.

When to avoid

Pixel-precise scroll math (use scroll position). Element resize, not visibility (use ResizeObserver). Old browsers without a polyfill.

Common pitfall: Re-creating the observer on every render thrashes performance — depend only on the serialized options, and disconnect in cleanup. The observer is async: the first entry may arrive a frame after mount.

Examples
function useIntersectionObserver(options?: IntersectionObserverInit) {
  const [entry, setEntry] = useState<IntersectionObserverEntry | null>(null);
  const ref = useRef<HTMLElement | null>(null);
  useEffect(() => {
    const node = ref.current;
    if (!node) return;
    const io = new IntersectionObserver(([e]) => setEntry(e), options);
    io.observe(node);
    return () => io.disconnect();
  }, [options?.root, options?.rootMargin, options?.threshold]);
  return [ref, entry] as const;
}

useDocumentTitle

Custom
SignatureuseDocumentTitle(title: string, restoreOnUnmount = false)

Set document.title to the given string and keep it in sync. Optionally restore the previous title on unmount. A tiny effect-driven sync to a non-React global.

When to use

Reflect the current route / item name in the tab title in a client-rendered SPA. Show an unread count like "(3) Inbox".

When to avoid

SSR / meta frameworks — use the framework head API (Next metadata, Astro <title>) so crawlers and social previews see it. Document title is client-only here.

Common pitfall: Setting document.title in render (not an effect) is a side effect during render and breaks concurrent rendering. Always do it in useEffect.

Examples
function useDocumentTitle(title: string, restore = false) {
  const prev = useRef(typeof document !== 'undefined' ? document.title : '');
  useEffect(() => {
    document.title = title;
  }, [title]);
  useEffect(() => {
    const previous = prev.current;
    return () => { if (restore) document.title = previous; };
  }, [restore]);
}

useEventCallback

Custom
Signatureconst stable = useEventCallback<A extends any[], R>(fn: (...a: A) => R)

Return a function with a STABLE identity that always calls the latest version of fn. A userland version of the proposed useEffectEvent — lets you read fresh props/state inside a callback without adding deps.

When to use

A handler passed to many memoized children that must stay referentially stable yet read fresh state. Avoiding "the function changes every render" deps churn.

When to avoid

Do NOT call the returned function during render — it intentionally reads the latest closure and is only safe in effects / event handlers. For values React itself diffs, prefer real deps.

Common pitfall: Calling it during render returns last-commit values and can tear under concurrent rendering. React ships useEffectEvent for exactly this; the userland version is a stopgap.

Examples
function useEventCallback<A extends any[], R>(fn: (...a: A) => R) {
  const ref = useRef(fn);
  useLayoutEffect(() => { ref.current = fn; });
  return useCallback((...args: A) => ref.current(...args), []);
}
const onScroll = useEventCallback(() => log(pageName, window.scrollY));

useThrottle

Custom
Signatureconst throttled = useThrottle<T>(value, intervalMs)

Return a value that updates at most once per intervalMs, even if the input changes faster. Unlike debounce (which waits for quiet), throttle guarantees a steady update cadence.

When to use

Scroll / mousemove driven UI that must update during the gesture, not after it stops. Live progress that should tick at a fixed rate.

When to avoid

Search-as-you-type, where you only care about the final value after a pause (use debounce). Cases where useDeferredValue already smooths the heavy render.

Common pitfall: A leading-only throttle drops the FINAL value if it arrives within the window — schedule a trailing update so the last value is not lost. Clear the timer in cleanup.

Examples
function useThrottle<T>(value: T, interval = 200): T {
  const [throttled, setThrottled] = useState(value);
  const last = useRef(Date.now());
  useEffect(() => {
    const now = Date.now();
    const remaining = interval - (now - last.current);
    if (remaining <= 0) { last.current = now; setThrottled(value); return; }
    const id = setTimeout(() => { last.current = Date.now(); setThrottled(value); }, remaining);
    return () => clearTimeout(id);
  }, [value, interval]);
  return throttled;
}

useScrollLock

Custom
SignatureuseScrollLock(locked: boolean)

Lock or unlock body scrolling. When locked, sets overflow:hidden on documentElement and restores the previous value on unlock / unmount. Standard for modals and drawers.

When to use

Open modal / drawer / full-screen menu where background scroll should freeze. Image lightboxes.

When to avoid

Native <dialog> with showModal() (the browser already blocks background scroll). When multiple lockers can overlap — you need a counter, not a boolean, or unlocking one re-enables scroll prematurely.

Common pitfall: On iOS Safari, overflow:hidden alone does not always stop the rubber-band scroll — you may also need to fix the body position and restore scrollTop. Always save and restore the original overflow value, not assume "auto".

Examples
function useScrollLock(locked: boolean) {
  useLayoutEffect(() => {
    if (!locked) return;
    const root = document.documentElement;
    const prev = root.style.overflow;
    root.style.overflow = 'hidden';
    return () => { root.style.overflow = prev; };
  }, [locked]);
}

useControllableState

Custom
Signatureconst [state, setState] = useControllableState<T>({ value, defaultValue, onChange })

Support BOTH controlled and uncontrolled usage in one component. Uses props.value when provided (controlled), otherwise internal state (uncontrolled), and calls onChange either way.

When to use

Reusable inputs / toggles / selects in a design system that consumers may want to control or let manage themselves — like Radix and React Aria components do.

When to avoid

A component that is always controlled or always uncontrolled — the dual logic is pure overhead then. Deeply nested form state better handled by a form library.

Common pitfall: Switching a component between controlled and uncontrolled across renders (value going from undefined to a value) warns and resets — decide the mode once at mount via whether value is initially defined.

Examples
function useControllableState<T>({ value, defaultValue, onChange }: {
  value?: T; defaultValue?: T; onChange?: (v: T) => void;
}) {
  const isControlled = value !== undefined;
  const [uncontrolled, setUncontrolled] = useState(defaultValue);
  const state = isControlled ? value : uncontrolled;
  const setState = useCallback((next: T) => {
    if (!isControlled) setUncontrolled(next);
    onChange?.(next);
  }, [isControlled, onChange]);
  return [state, setState] as const;
}

useRenderCount

Custom
Signatureconst renders = useRenderCount(): number

Return how many times the component has rendered. A debugging hook that increments a ref every render. Helps spot unexpected re-renders during optimization work.

When to use

Temporarily during performance debugging to confirm whether memoization actually cut re-renders. Logging in development to find render storms.

When to avoid

Production code / business logic — render count is an implementation detail and doubles under StrictMode dev. Never branch real behavior on it.

Common pitfall: In React 18 StrictMode dev, components render twice on mount, so the count is inflated. Reading it during render and mutating a ref is fine here, but do not depend on its exact value.

Examples
function useRenderCount() {
  const count = useRef(0);
  count.current += 1;
  return count.current;
}
const renders = useRenderCount();
console.log('Table rendered', renders, 'times');

useStateWithHistory

Custom
Signatureconst [state, set, { undo, redo, canUndo, canRedo }] = useStateWithHistory<T>(initial)

State plus an undo/redo history stack. Each set pushes onto the history; undo / redo move a pointer through it. The pointer model keeps redo available until a new value branches the timeline.

When to use

Editors, drawing canvases, form builders — anywhere Ctrl+Z / Ctrl+Y is expected. Step-through of state for debugging.

When to avoid

Large or fast-changing state — the history grows unbounded in memory unless you cap it. High-frequency updates (cap or debounce what gets pushed).

Common pitfall: Setting a new value while the pointer is in the middle of history must TRUNCATE the redo tail — otherwise redo replays a branch the user already abandoned. Cap the stack length to avoid memory growth.

Examples
function useStateWithHistory<T>(initial: T) {
  const [history, setHistory] = useState<T[]>([initial]);
  const [ptr, setPtr] = useState(0);
  const set = useCallback((value: T) => {
    setHistory(h => [...h.slice(0, ptr + 1), value]);
    setPtr(p => p + 1);
  }, [ptr]);
  const undo = useCallback(() => setPtr(p => Math.max(0, p - 1)), []);
  const redo = useCallback(() => setPtr(p => Math.min(history.length - 1, p + 1)), [history.length]);
  return [history[ptr], set, {
    undo, redo, canUndo: ptr > 0, canRedo: ptr < history.length - 1,
  }] as const;
}

useKeyPress

Custom
SignatureuseKeyPress(targetKey: string, handler: (e: KeyboardEvent) => void)

Run a handler when a specific key is pressed on the window. Matches against KeyboardEvent.key (e.g. "Escape", "Enter", "k"). Refs the handler so it always sees fresh state.

When to use

Close a modal on Escape. Submit on Enter. Single-key shortcuts. Pair with a modifier check for combos like Cmd+K.

When to avoid

Typing inside inputs — guard against firing shortcuts while a field is focused. Complex chords / sequences (use a dedicated keybinding library).

Common pitfall: Match on event.key, not event.keyCode (deprecated). "Escape" is the key value, not "Esc". Without a handler ref, every render re-subscribes the listener.

Examples
function useKeyPress(targetKey: string, handler: (e: KeyboardEvent) => void) {
  const ref = useRef(handler);
  useEffect(() => { ref.current = handler; }, [handler]);
  useEffect(() => {
    const onKey = (e: KeyboardEvent) => {
      if (e.key === targetKey) ref.current(e);
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [targetKey]);
}
useKeyPress('Escape', () => setOpen(false));

useDebouncedCallback

Custom
Signatureconst debounced = useDebouncedCallback<A extends any[]>(fn: (...a: A) => void, delayMs)

Debounce a CALLBACK (not a value): returns a stable function that delays invoking fn until delayMs of quiet. Each call resets the timer; the latest arguments win. Cancels on unmount.

When to use

Fire an API call on the trailing edge of typing while passing the event/args through. Auto-save where you call save(payload) with the latest payload.

When to avoid

Debouncing a controlled input VALUE (use useDebounce on the value, never on the onChange that sets it). Leading-edge needs (this is trailing-edge).

Common pitfall: Returning a new function each render breaks debouncing — the timer lives in a ref and the returned function must be stable. Always clear the pending timer in cleanup or rapid unmounts leak timers.

Examples
function useDebouncedCallback<A extends any[]>(fn: (...a: A) => void, delay = 300) {
  const fnRef = useRef(fn);
  useEffect(() => { fnRef.current = fn; }, [fn]);
  const timer = useRef<ReturnType<typeof setTimeout>>();
  useEffect(() => () => clearTimeout(timer.current), []);
  return useCallback((...args: A) => {
    clearTimeout(timer.current);
    timer.current = setTimeout(() => fnRef.current(...args), delay);
  }, [delay]);
}
const save = useDebouncedCallback((draft: Draft) => api.save(draft), 800);

useFavicon

Custom
SignatureuseFavicon(href: string)

Swap the page favicon at runtime by updating (or creating) the <link rel="icon"> element. A small effect-driven sync to the document head, like useDocumentTitle but for the icon.

When to use

Show an unread / alert badge favicon. Reflect environment (red icon on staging). Theme the icon to match dark / light mode.

When to avoid

SSR / static meta — set the favicon in the HTML head so it loads before hydration. This is for client-side runtime changes only.

Common pitfall: Creating a new <link> each call piles up duplicate icon links. Query for the existing rel="icon" link first and reuse it; only create one if none exists.

Examples
function useFavicon(href: string) {
  useEffect(() => {
    let link = document.querySelector<HTMLLinkElement>('link[rel="icon"]');
    if (!link) {
      link = document.createElement('link');
      link.rel = 'icon';
      document.head.appendChild(link);
    }
    link.href = href;
  }, [href]);
}
useFavicon(unread > 0 ? '/favicon-alert.ico' : '/favicon.ico');

What this tool does

Searchable React hooks cheat sheet that covers all 17 built-in hooks shipped with React 18 + 19 plus ten of the custom-hook patterns every codebase eventually grows. For each hook you get the function signature, a one-paragraph bilingual EN/ZH description, an explicit "when to use" vs "when to avoid" pair, the one common pitfall that bites real teams (stale closures in useEffect, missing AbortController in useFetch, getSnapshot returning a fresh object every render in useSyncExternalStore, calling useFormStatus on the same component that owns the form), and 1-3 copy-ready examples. State hooks (useState, useReducer). Effect hooks (useEffect, useLayoutEffect, useInsertionEffect — yes, that one too, including why you almost never need it). Memoization (useMemo, useCallback — with the anti-pattern callout that wrapping every value makes things slower). Refs (useRef and the useImperativeHandle + forwardRef pairing). Context (useContext + the inline-object Provider trap). Concurrent rendering (useTransition, useDeferredValue + the React.memo requirement on heavy children). External stores (useSyncExternalStore with concurrent-safe bindings). Accessibility (useId and why its colons break naive querySelector). The four React 19 additions (useOptimistic, useActionState, useFormStatus, the unified use() read primitive). And ten custom-hook patterns straight out of production code: useToggle, useLocalStorage, useDebounce, useFetch, useMediaQuery, useEventListener, useOnClickOutside, usePrevious, useWindowSize, useInterval. Filter by built-in vs custom, search across name / signature / description / examples / pitfalls simultaneously, one-click copy on every signature. Pure client-side, no upload, no tracking.

Tool details

Input
Files + Text
The page exposes text boxes, numeric controls, file pickers, or structured inputs depending on the tool.
Output
Live result + Copy + Preview
The result area focuses on usable output, with copy, download, or preview actions when supported.
Privacy
May use a live lookup
A network call is detected in the component, so redact sensitive data when appropriate.
Save / share
Local preference storage
Preferences, history, or drafts are saved in this browser without an account.
Performance budget
Initial JS <= 30 KB
No WASM budget is declared, keeping the tool quick to open on mobile.
Best fit
Developer & DevOps · Developer
Category and role tags drive related tools, internal links, and quick fit checks.

How to use

  1. 1. Input

    Paste or drop your content into the tool panel.

  2. 2. Process

    Click the button. All processing is local in your browser.

  3. 3. Copy / Download

    Copy the result or download to disk in one click.

How React Hooks Cheatsheet fits into your work

Use it in the small gaps between coding, reviewing, debugging, and shipping.

Developer jobs

  • Formatting, validating, shrinking, or inspecting code-adjacent text.
  • Preparing snippets for documentation, tickets, commits, or handoff.
  • Checking a small payload quickly without switching tools.

Developer checks

  • Run irreversible transforms like minify or obfuscate on a copy.
  • Keep secrets out of pasted snippets unless the tool explicitly stays local.
  • Use your normal tests or linter before shipping transformed code.

Good next steps

These links move the current task into a more complete workflow.

  1. 1 JSON Formatter & Validator Format, validate, and minify JSON instantly — right in your browser. Open
  2. 2 TypeScript Cheatsheet TypeScript cheat sheet — 100+ snippets for types, generics, utility types, narrowing, async patterns. Open
  3. 3 Python Cheatsheet Python cheat sheet — 100+ idiomatic Python snippets for string, list, dict, file, async, with real examples. Open

Real-world use cases

  • A junior dev pings you "useEffect runs twice" mid-PR review

    You paste the cheat sheet link instead of typing a wall of Slack. They search "useEffect", read the 6-line pitfall on Strict Mode double-invocation plus the cleanup-function fix, and copy the AbortController snippet. The PR thread closes in 4 minutes instead of a 20-minute call, and they hit the same entry the next two times without asking again.

  • Migrating a React 18 app to 19 and you forget which hooks are new

    Before touching the form code you filter to the four React 19 additions, confirm useFormStatus must live in a child of the form (not the same component), and read the useActionState signature so you wire the action and pending state correctly on the first try. You skip the hour you would have lost to the { pending: false } trap.

  • Prepping a 30-minute "hooks deep dive" for three new hires

    You walk the team through the 17 built-ins on one screen, using the when-to-use vs when-to-avoid pairs as discussion prompts. The useMemo "wrapping every value is slower" callout and the useDeferredValue React.memo requirement spark the exact debate you wanted, and everyone leaves with the same copyable reference.

  • Debugging an infinite render loop you can't pin down

    The screen flickers and React warns about too many re-renders. You search "useSyncExternalStore" on a hunch, see the pitfall that getSnapshot returning a fresh object every render triggers an infinite loop, cache the snapshot with a stable reference, and the loop stops. Total time: under five minutes, no Stack Overflow tab.

Common pitfalls

  • Treating useEffect like a lifecycle hook and chaining state updates inside it. If you setState in an effect that depends on that same state, you get an extra render or a loop; derive the value during render instead, e.g. const full = `${first} ${last}` not a useEffect that sets it.

  • Wrapping every value in useMemo/useCallback "to be safe". The comparison and cache cost real cycles; only memoize values that are expensive to compute or are passed to a React.memo child, like a 10k-row sort, not `useMemo(() => a + b, [a, b])`.

  • Calling useFormStatus in the same component that renders the <form>. It only reads the status of a parent form, so its pending flag stays false forever; move the submit button and its useFormStatus call into a child component of the form.

Privacy

This cheat sheet is a single static page. Your search box text and the built-in/custom filter run entirely in your browser against an in-memory hook array, never sent anywhere. The current search and filter are stored in the URL so you can share or bookmark a view, but that URL stays on your machine until you choose to share it. No code execution, no upload, no analytics on what you type.

FAQ

Tool combos

Folks in your role tend to reach for these alongside this tool.

Made by Toolora · 100% client-side · Updated 2026-07-02