---
title: "React Hooks Cheatsheet"
slug: "react-hooks"
category: "cheatsheets"
tags: ["cheatsheets", "react", "hooks", "frontend", "reference", "javascript", "typescript"]
status: "stable"
last_updated: 2026-05-14
summary: "React hooks reference: useState, useEffect, useMemo, useCallback, useRef, useContext, useReducer, and the rules of hooks with common patterns."
related:
  [
    "[[frontend/react-hooks]]",
    "[[frontend/react-performance]]",
    "[[frontend/react-state-management]]",
    "[[frontend/react]]",
    "[[frontend/react-forms]]",
    "[[cheatsheets/typescript-utility-types]]",
    "[[cheatsheets/javascript-array-methods]]",
    "[[cheatsheets/index|Cheatsheets]]",
  ]
---

## Overview

Hooks let function components manage state, side-effects, and shared logic without class components. This card is the lookup table; the performance trade-offs between `useMemo`, `useCallback`, and `memo` are discussed in [[frontend/react-performance]]. All examples use TypeScript generics where types are non-obvious.

## Rules of hooks (non-negotiable)

Violating these causes silent bugs or runtime crashes.

| Rule | What it means | Why |
| --- | --- | --- |
| Call only at the top level. | Never inside conditions, loops, or nested functions. | React tracks hooks by call order. Branching breaks the order. |
| Call only from React functions. | Function components and custom hooks only, not plain JS functions. | React must own the component lifecycle to manage state. |
| Prefix custom hooks with `use`. | `useMyHook`, not `myHook`. | Enables lint rules and signals hook semantics to readers. |

The `react-hooks/rules-of-hooks` ESLint rule enforces these automatically.

## useState

| Signature | `const [state, setState] = useState<T>(initialValue)` |
| --- | --- |
| Lazy init | `useState(() => expensiveCompute())`, function called only once. |
| Functional update | `setState(prev => prev + 1)`, safe when new state depends on old. |
| Object state | Update with spread: `setState(prev => ({ ...prev, key: value }))`. |
| Reset | `setState(initialValue)`, no special API; just set it back. |

```typescript
const [count, setCount] = useState(0);
const [user, setUser] = useState<User | null>(null);

// Functional update avoids stale closure
setCount(prev => prev + 1);

// Partial object update
setUser(prev => prev ? { ...prev, name: "Alice" } : null);
```

## useEffect

| Pattern | Code | Meaning |
| --- | --- | --- |
| Run on every render | `useEffect(() => { ... })` | No deps array; usually wrong. |
| Run once on mount | `useEffect(() => { ... }, [])` | Empty deps; replaces `componentDidMount`. |
| Run when deps change | `useEffect(() => { ... }, [a, b])` | Re-runs when `a` or `b` changes (referential equality). |
| Cleanup | `return () => { ... }` inside the effect | Runs before the effect re-runs and on unmount. |
| Async inside | Wrap in an immediately-invoked async function; don't make the effect itself async. | `useEffect` must return either nothing or a cleanup function. |

```typescript
useEffect(() => {
  const controller = new AbortController();
  fetch("/api/data", { signal: controller.signal })
    .then(r => r.json())
    .then(setData)
    .catch(() => {}); // swallow abort errors
  return () => controller.abort();
}, []);
```

## useMemo and useCallback

Both accept a deps array. Omit only when the computation is trivial.

| Hook | Returns | Use when |
| --- | --- | --- |
| `useMemo(() => compute(), deps)` | Memoized value. | The computation is expensive (sort, filter, map on large arrays). |
| `useCallback(fn, deps)` | Memoized function reference. | Passing a callback to a memoized child that uses `React.memo`. |

```typescript
// useMemo: sorted list only recomputes when items changes
const sorted = useMemo(
  () => [...items].sort((a, b) => a.name.localeCompare(b.name)),
  [items]
);

// useCallback: stable reference for memoized child
const handleClick = useCallback((id: number) => {
  dispatch({ type: "select", id });
}, [dispatch]);
```

Do not wrap every value in `useMemo`. The memoization overhead costs more than a cheap recalculation.

## useRef

| Use case | Pattern | Notes |
| --- | --- | --- |
| DOM node reference | `const ref = useRef<HTMLInputElement>(null); <input ref={ref} />` | `ref.current` is the DOM element after mount. |
| Mutable instance variable | `const countRef = useRef(0); countRef.current++` | Does not trigger re-render when changed. |
| Persist values across renders | `const prevValue = useRef(value)` | Store previous render's value without state. |
| Stable callback ref | `const cbRef = useRef(cb); useEffect(() => { cbRef.current = cb }, [cb])` | Lets an interval/listener always call the latest version. |

`useRef` is the right choice when you need a mutable value that does not affect render output.

## useContext and useReducer

| Hook | Signature | When to use |
| --- | --- | --- |
| `useContext(Ctx)` | Returns current context value. | Read theme, auth, locale; avoid for high-frequency updates. |
| `useReducer(reducer, init)` | `[state, dispatch]` pair. | State with multiple sub-values or complex transitions; replaces multiple `useState` calls. |

```typescript
// useReducer for a form with multiple fields
type Action =
  | { type: "set_name"; value: string }
  | { type: "set_email"; value: string }
  | { type: "reset" };

function formReducer(state: FormState, action: Action): FormState {
  switch (action.type) {
    case "set_name":  return { ...state, name: action.value };
    case "set_email": return { ...state, email: action.value };
    case "reset":     return initialState;
  }
}

const [form, dispatch] = useReducer(formReducer, initialState);
```

## Common gotchas

- `useEffect` with an object or array dep fires on every render because referential equality fails. Memoize the dep with `useMemo` or move it outside the component.
- `useCallback(fn, [])` with a stale closure captures the values at mount time. Add all values the callback reads to the deps array.
- `useRef` changes do not trigger re-renders. Reading `ref.current` in JSX will show stale values. Use state for anything that must re-render.
- `useContext` re-renders every consumer when the context value changes, even if the consumer only reads one field. Split contexts or memoize the value with `useMemo`.
- `useMemo` and `useCallback` are hints, not guarantees. React may discard memoized values between renders (e.g., in React 18 Concurrent Mode). Do not store side-effectful logic inside them.
- Calling `setState` inside `useEffect` without correct deps creates infinite loops. Always ask: does this effect write to a dep it also reads?

## Related

- [[frontend/react-hooks]]
- [[frontend/react-performance]]
- [[frontend/react-state-management]]
- [[frontend/react]]
- [[frontend/react-forms]]
- [[cheatsheets/typescript-utility-types]]
- [[cheatsheets/javascript-array-methods]]
- [[cheatsheets/index|Cheatsheets]]
