# useState

## 1. The Basic API

`useState` gives a function component a piece of state that survives across re-renders — something a plain function cannot do on its own, since a plain function's local variables are recreated (and forgotten) on every call.

```tsx
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0); // 0 is the INITIAL value, used only on mount

  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );
}
```

`useState(initialValue)` returns a `[value, setter]` pair. Calling the setter does two things: it records the new value, and it tells React "this component needs to re-render." The `count` variable in the *next* render's function call carries the updated value — `count` itself never mutates in place.

## 2. Lazy Initialization

If computing the initial value is expensive, pass a **function** instead of a value — React calls it only once, on mount, never on subsequent renders:

```tsx
function ExpensiveList() {
  // BAD: parseHugeDataset() runs on every single render, then the result is thrown away
  // const [items, setItems] = useState(parseHugeDataset(rawData));

  // GOOD: the function itself is only invoked on mount
  const [items, setItems] = useState(() => parseHugeDataset(rawData));
  return <ul>{items.map((i) => <li key={i.id}>{i.label}</li>)}</ul>;
}
```

Passing `useState(parseHugeDataset(rawData))` still calls `parseHugeDataset` on every render (the call happens before `useState` ever sees the result) — only the lazy-initializer form avoids that.

## 3. Functional Updates

The setter accepts either a new value directly, or an **updater function** `(prevState) => newState`. The updater form is the only safe way to compute new state from old state when the update doesn't happen synchronously inside the render that reads it:

```tsx
function Counter() {
  const [count, setCount] = useState(0);

  function handleTripleClick() {
    // BUG: all three calls close over the SAME `count` value from this render
    setCount(count + 1); // schedules "set to 0 + 1"
    setCount(count + 1); // schedules "set to 0 + 1" — same stale `count`
    setCount(count + 1); // schedules "set to 0 + 1" — same stale `count`
    // result: count becomes 1, not 3
  }

  function handleTripleClickFixed() {
    // CORRECT: each updater receives the LATEST pending state, not the render's closure value
    setCount((prev) => prev + 1);
    setCount((prev) => prev + 1);
    setCount((prev) => prev + 1);
    // result: count becomes 3
  }

  return <button onClick={handleTripleClickFixed}>{count}</button>;
}
```

Rule of thumb: if the new state depends on the previous state, use the function form.

## 4. Batching

React batches multiple `setState` calls that occur within the same event handler (and, since React 18, within promises, `setTimeout`, and native event handlers too) into a single re-render, rather than re-rendering once per call:

```tsx
function handleClick() {
  setCount((c) => c + 1);
  setFlag((f) => !f);
  // Only ONE re-render happens after handleClick finishes, not two —
  // React 18's "automatic batching" applies everywhere, not just inside
  // React event handlers like it did pre-18.
}
```

This is why reading `count` immediately after calling `setCount(count + 1)` in the same function still shows the old value — the state variable in the *current* closure never changes; only the *next* render's `count` reflects the update.

## 5. State Must Be Replaced, Not Mutated

React decides whether to re-render (for objects/arrays passed to `setState`) partly by comparing the old and new references. Mutating an object/array in place and passing the *same reference* back to `setState` means React sees no change and may skip re-rendering:

```tsx
function TodoList() {
  const [todos, setTodos] = useState<string[]>([]);

  function addBad(todo: string) {
    todos.push(todo);        // mutates the existing array in place
    setTodos(todos);         // same reference — React may bail out, UI doesn't update
  }

  function addGood(todo: string) {
    setTodos((prev) => [...prev, todo]); // NEW array reference — always triggers a render
  }
}
```

The same applies to objects: `setUser({ ...user, name: "New" })`, never `user.name = "New"; setUser(user)`.

## 6. useState vs useReducer

`useState` is a thin, general-purpose wrapper — internally, React actually implements it on top of the exact same reducer mechanism `useReducer` uses (a built-in reducer that just replaces the value). Reach for `useReducer` instead when: several state fields update together as one atomic transition, the next state depends on complex logic that's easier to test as a pure function, or update logic should be described declaratively as named "actions" rather than ad-hoc setter calls. See the useReducer topic for details.

![useState(0) creates one node in the fiber's hook linked list holding the current value and a pending update queue](diagrams/01-fiber-hook-linked-list.svg)

## Under the Hood

React does not attach state to the component *function* — a function component is just a function, called fresh every render with no persistent `this` and no memory of its own. State has to live somewhere else: it lives on the **fiber**, React's internal object representing one component instance in the tree.

Each fiber holds a `memoizedState` pointer to the head of a **singly linked list of hook objects**, one node per hook call, built in the exact order those hooks are called. Every render, React walks this list from the head, one node per hook call, in lockstep with your function body executing top to bottom: the first `useState()` call reads/writes node 1, the second reads/writes node 2, and so on. Matching is done purely by **call order** — not by variable name, not by any key. This is the entire reason the Rules of Hooks forbid conditional hook calls: "the 3rd hook call in this render" is React's only address for "hook 3's stored data." Wrap a `useState` in an `if` and, on a render where the branch is skipped, every hook call after it shifts one slot to the left in the walk — silently reading and writing the wrong hook's state, usually with no error thrown at all.

On mount, React builds this list node by node, allocating a fresh object as each hook is called for the first time. On every later render, React reuses the same node objects — it does not rebuild the list — it just reads and updates whatever is already sitting in each node as the walk reaches it.

**`useState`'s own node** stores two things: (1) a `memoizedState` slot holding the *current, committed* value, and (2) a queue of pending updates. Calling the setter does not mutate that slot synchronously — it pushes an update object (either a plain value or an updater function) onto that hook's queue and calls `scheduleUpdateOnFiber`, which marks the fiber and its ancestors as needing work and schedules a re-render. On the *next* render, before your component body runs, React walks that hook's pending-update queue front to back, applies each queued update in order (calling the updater function with the running result so far, or just overwriting with a plain value), and writes the final result into `memoizedState`. That's why `count` read anywhere inside the current render is always "this render's committed value" — it can never change mid-render, and it's also why three back-to-back functional updates in one handler correctly compound (each updater in the queue sees the previous updater's result), while three back-to-back plain-value updates using the same stale closured `count` overwrite each other instead of compounding.

## Common Pitfalls

1. **Reading `count` right after calling `setCount`** and expecting the updated value in the same render — the setter schedules a future render; the current closure's variable never changes.
2. **Using `setCount(count + 1)` multiple times in one handler** when the intent is to increment multiple times — each call closes over the same stale `count`; use the functional updater `setCount(c => c + 1)` instead.
3. **Mutating an object/array then calling the setter with the same reference** — React's bailout check can skip the re-render entirely since the reference didn't change.
4. **Calling `useState` conditionally or inside a loop** — breaks the fixed call-order the fiber's hook linked list depends on.
5. **Passing an expensive expression directly to `useState(...)`** instead of `useState(() => ...)` — the expression re-evaluates (and is discarded) on every render, not just on mount.

## Quick Reference

```
const [value, setValue] = useState(initial)     → basic state
useState(() => compute())                        → lazy initializer, runs once on mount
setValue(newValue)                                → replace with a plain value
setValue(prev => prev + 1)                        → functional update, safe from stale closures
setValue({ ...obj, key: newVal })                 → replace objects immutably
setValue([...arr, item])                          → replace arrays immutably
```
