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.
28 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 (10)

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);

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

FAQ

Tool combos

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

Made by Toolora · 100% client-side · Updated 2026-05-29