React hooks cheatsheet.

Rules

  • Hooks only at top level — no loops, conditions, nested functions.
  • Only in components or other hooks.
  • Don’t rename React’s hooks.

useState

const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);
const [data, setData] = useState(() => heavyInit());      // lazy

Updater form:

setCount((c) => c + 1);     // safe for concurrent / batched updates

useEffect

useEffect(() => {
  // side effect
  return () => {
    // cleanup
  };
}, [dep1, dep2]);

Skipping the array runs every render. Empty [] runs once.

useLayoutEffect

useLayoutEffect(() => {
  // runs synchronously AFTER DOM mutations, BEFORE paint
  // Use to measure DOM, avoid flicker
}, [deps]);

Only use when you need to read layout before paint.

useMemo

const result = useMemo(() => expensiveCompute(data), [data]);

Cache expensive values. Don’t sprinkle everywhere — only when measurable.

useCallback

const onClick = useCallback((id: number) => doIt(id), [doIt]);

Cache function identity. Useful when passing to memoized children or as a hook dep.

useRef

const ref = useRef<HTMLDivElement>(null);
<div ref={ref} />

// Mutable value (not state):
const idRef = useRef(0);
idRef.current++;       // doesn't trigger re-render

useReducer

type State = { count: number };
type Action = { type: "inc" } | { type: "dec" };

function reducer(s: State, a: Action): State {
  switch (a.type) {
    case "inc": return { count: s.count + 1 };
    case "dec": return { count: s.count - 1 };
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0 });
dispatch({ type: "inc" });

Better than useState when state transitions are complex.

useContext

const Theme = createContext("light");

function Comp() {
  const theme = useContext(Theme);
  return <p>{theme}</p>;
}

useId

const id = useId();
return (
  <>
    <label htmlFor={id}>Name</label>
    <input id={id} />
  </>
);

Stable, unique ID per render tree position. SSR-safe.

useTransition

const [isPending, startTransition] = useTransition();

startTransition(() => {
  setHeavyState(newValue);    // marked as non-urgent
});

return isPending ? <Spinner /> : <List />;

Lower-priority updates that yield to user input.

useDeferredValue

const deferred = useDeferredValue(input);
// Renders with deferred during heavy updates; eventually catches up

useSyncExternalStore

const value = useSyncExternalStore(
  (cb) => store.subscribe(cb),
  () => store.getSnapshot(),
  () => store.getServerSnapshot?.(),
);

Subscribe to external (non-React) state safely with concurrent rendering.

useImperativeHandle

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

Use sparingly — usually a sign of fighting the framework.

useDebugValue (devtools only)

function useUser(id: number) {
  const u = ...;
  useDebugValue(u ? `user ${u.id}` : "loading");
  return u;
}

Custom hooks

function useToggle(initial = false) {
  const [on, setOn] = useState(initial);
  const toggle = useCallback(() => setOn((v) => !v), []);
  return [on, toggle] as const;
}

const [open, toggle] = useToggle();

Convention: name starts with use.

use (React 19+)

function Comp() {
  const data = use(fetchPromise);      // suspends until resolved
  return <div>{data}</div>;
}

Can be called conditionally, unlike other hooks. Works with Promises and Contexts.

useOptimistic (React 19+)

const [optimisticItems, addOptimistic] = useOptimistic(items, (state, newItem) => [...state, newItem]);

async function submit(item: Item) {
  addOptimistic(item);
  await save(item);
}

Immediate UI update, rollback on failure.

useActionState (React 19+)

const [state, formAction, isPending] = useActionState(
  async (prev, formData) => { ... },
  initialState,
);

<form action={formAction}>...</form>

useFormStatus (React 19+)

function Submit() {
  const { pending } = useFormStatus();
  return <button disabled={pending}>Save</button>;
}

Read parent <form> action state.

Common mistakes

  • Hook inside if — breaks rule of hooks.
  • Stale closure — function captures old state. Use updater form or refs.
  • Missing dep — react-hooks/exhaustive-deps catches it.
  • Object/array deps not memoized — effect fires every render.
  • useEffect for derived state — compute during render instead.

Read this next

If you want my custom hooks library, it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .