React Cheatsheet

More Hooks

Use this React reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

useRef

Stores a mutable value that does not trigger re-renders. Also used to access DOM nodes.

import { useRef, useEffect } from 'react';

// DOM reference
function TextInput() {
  const inputRef = useRef(null);        // initial value: null
  const focus = () => inputRef.current.focus();
  return (
    <>
      <input ref={inputRef} />
      <button onClick={focus}>Focus</button>
    </>
  );
}

// Mutable instance variable (persists across renders, no re-render on change)
function Timer() {
  const intervalRef = useRef(null);
  const [count, setCount] = useState(0);

  const start = () => {
    intervalRef.current = setInterval(() => setCount(c => c + 1), 1000);
  };
  const stop = () => clearInterval(intervalRef.current);

  return <><p>{count}</p><button onClick={start}>Start</button><button onClick={stop}>Stop</button></>;
}

// Hold previous value of a prop/state
function usePrevious(value) {
  const prev = useRef(undefined);
  useEffect(() => { prev.current = value; });
  return prev.current; // returns value from previous render
}

ref.current is read/written directly; mutating it is fine and does not cause re-renders.

useReducer

Alternative to useState when state logic is complex or involves multiple sub-values.

import { useReducer } from 'react';

// 1. Define a reducer — pure function (state, action) => newState
function counterReducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    case 'reset':     return { count: 0 };
    case 'set':       return { count: action.payload };
    default: throw new Error('Unknown action: ' + action.type);
  }
}

// 2. Use it
function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });

  return (
    <div>
      <p>{state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'set', payload: 10 })}>Set 10</button>
    </div>
  );
}

// Lazy initializer (third arg) — runs once
const [state, dispatch] = useReducer(reducer, userId, (id) => ({
  user: null, loading: true, id
}));

useMemo

Memoize an expensive computation — recomputes only when dependencies change.

import { useMemo } from 'react';

function FilteredList({ items, query }) {
  // Recomputes only when items or query changes
  const filtered = useMemo(
    () => items.filter(i => i.name.includes(query)),
    [items, query]
  );

  return <ul>{filtered.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}

// Stable object reference (avoids re-renders of memoized children)
const options = useMemo(() => ({ threshold: 0.5 }), []); // stable forever

Do not overuse. useMemo itself has a cost. Only memoize when a profiler shows the computation is actually slow, or when you need a stable reference.

useCallback

Memoize a function reference — returns the same function instance until deps change.

import { useCallback } from 'react';

function Parent({ userId }) {
  const [items, setItems] = useState([]);

  // Without useCallback, a new function is created every render →
  // memoized children (React.memo) would still re-render
  const handleDelete = useCallback(
    (id) => setItems(prev => prev.filter(i => i.id !== id)),
    [] // setItems is stable, so no deps needed
  );

  return <MemoizedList items={items} onDelete={handleDelete} />;
}

// Equivalent: useMemo returning a function
const handleDelete = useMemo(
  () => (id) => setItems(prev => prev.filter(i => i.id !== id)),
  []
);

useCallback(fn, deps) is sugar for useMemo(() => fn, deps).

useContext

Read a context value. (See Context page for createContext and Provider.)

import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';

function Button({ children }) {
  const { theme, toggleTheme } = useContext(ThemeContext);
  return (
    <button
      className={theme === 'dark' ? 'btn-dark' : 'btn-light'}
      onClick={toggleTheme}
    >
      {children}
    </button>
  );
}

useId

Generate a stable, unique ID per component instance. Use for accessibility attributes.

import { useId } from 'react';

function LabeledInput({ label }) {
  const id = useId(); // e.g. "«r3»" (React 19; React 18 generated ":r3:")
  return (
    <div>
      <label htmlFor={id}>{label}</label>
      <input id={id} />
    </div>
  );
}
// Safe for SSR — same ID generated on server and client

useTransition

Mark state updates as non-urgent (can be interrupted by urgent updates).

import { useTransition } from 'react';

function Search() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    setQuery(e.target.value);             // urgent — update input immediately
    startTransition(() => {
      setResults(computeResults(e.target.value)); // non-urgent — can defer
    });
  };

  return (
    <>
      <input value={query} onChange={handleChange} />
      {isPending && <Spinner />}
      <ResultsList results={results} />
    </>
  );
}

useDeferredValue

Defer a value (not the setter) — the UI renders with the old value while the new one loads.

import { useDeferredValue } from 'react';

function SearchResults({ query }) {
  const deferredQuery = useDeferredValue(query);
  // deferredQuery lags behind query — heavy list renders with stale value
  const isStale = deferredQuery !== query;

  return (
    <div style={{ opacity: isStale ? 0.5 : 1 }}>
      <HeavyList query={deferredQuery} />
    </div>
  );
}

useImperativeHandle

Customize the handle a parent gets from a component's ref. In React 19, ref is a regular prop on function components, so no forwardRef wrapper is needed.

import { useImperativeHandle, useRef } from 'react';

// React 19 — ref arrives as a normal prop
function VideoPlayer({ src, ref }) {
  const videoRef = useRef(null);

  useImperativeHandle(ref, () => ({
    play:  ()       => videoRef.current.play(),
    pause: ()       => videoRef.current.pause(),
    seek:  (time)   => { videoRef.current.currentTime = time; },
  }));

  return <video ref={videoRef} src={src} />;
}

// React ≤18 (legacy): wrap in forwardRef to receive ref —
// const VideoPlayer = forwardRef(function VideoPlayer(props, ref) { ... });

// Parent
function App() {
  const playerRef = useRef(null);
  return (
    <>
      <VideoPlayer ref={playerRef} src="/movie.mp4" />
      <button onClick={() => playerRef.current.play()}>Play</button>
    </>
  );
}

useDebugValue

Label a custom hook in React DevTools.

import { useDebugValue, useState } from 'react';

function useOnlineStatus() {
  const [online, setOnline] = useState(navigator.onLine);
  // Shows "OnlineStatus: Online" in DevTools
  useDebugValue(online ? 'Online' : 'Offline');
  return online;
}

// With formatter (deferred — only called when DevTools are open)
useDebugValue(date, (d) => d.toISOString());

useSyncExternalStore

Subscribe to an external store (non-React state). The correct way to integrate third-party stores.

import { useSyncExternalStore } from 'react';

// Works with any store that exposes subscribe + getSnapshot
function useWindowWidth() {
  return useSyncExternalStore(
    (callback) => {
      window.addEventListener('resize', callback);
      return () => window.removeEventListener('resize', callback);
    },
    () => window.innerWidth,          // client snapshot
    () => 1024                        // server snapshot (SSR)
  );
}

Hook Rules

  1. Only call hooks at the top level — never inside conditions, loops, or nested functions.
  2. Only call hooks from React function components or custom hooks — not regular JS functions.
// WRONG — inside a condition
if (condition) {
  const [x, setX] = useState(0); // violates rule 1
}

// CORRECT — always at top level; condition goes inside the hook
const [x, setX] = useState(0);
if (condition) { setX(1); }

// eslint-plugin-react-hooks enforces these rules automatically

Hooks Quick Reference

HookPurpose
useStateLocal state with re-render
useReducerComplex state machine
useEffectSide effects (fetch, listeners, timers)
useLayoutEffectSide effects before paint
useRefDOM ref / mutable value without re-render
useContextRead context value
useMemoMemoize expensive computation
useCallbackStable function reference
useIdUnique accessible ID
useTransitionMark update as non-urgent
useDeferredValueDefer a derived value
useImperativeHandleCustomize the handle exposed via ref
useDebugValueDevTools label for custom hooks
useSyncExternalStoreSubscribe to external store
useActionStateForm/action state + pending flag (React 19 — see Forms)
useOptimisticOptimistic value while an action runs (React 19 — see Forms)
useRead a promise or context in render (React 19)