Home/Learn/React/useState — The Core of Everything

useState — The Core of Everything

Beginner
State & Effects

State is a snapshot for a given render, not a variable you read live. That one idea explains stale values, batching, and why you update with a function.

Overview

The common mental model is that setState assigns to a variable. It does not. Each render has its own frozen copy of state; calling the setter schedules a new render with a new value, and the current render keeps the old one until it is done. This is why reading state immediately after setting it gives the old value, why two increments in one handler can produce one increment, and why the functional updater exists. Once state is understood as a per-render snapshot, most beginner React bugs stop being mysterious.

State Is a Snapshot

The value does not change during a render, even after you set it.

Why two increments give one
const [count, setCount] = useState(0)

function handleClick() {
  setCount(count + 1)
  console.log(count)        // still 0 — this render's snapshot
}

// Two updates from the same snapshot collapse into one
function handleDouble() {
  setCount(count + 1)       // 0 + 1
  setCount(count + 1)       // 0 + 1 again -> ends at 1, not 2
}

// The functional updater reads the latest queued value
function handleDoubleFixed() {
  setCount(c => c + 1)      // 0 -> 1
  setCount(c => c + 1)      // 1 -> 2
}

// Rule of thumb: if the next value depends on the previous one,
// always use the function form.

Batching and Initialisation

React groups updates within the same tick, and lazy initialisation avoids repeating expensive setup.

Automatic batching and lazy init
// All three cause ONE re-render (automatic batching, React 18+)
function submit() {
  setLoading(false)
  setData(rows)
  setError(null)
}
// Batching applies inside promises, timeouts and native handlers too.

// Lazy initial state — the function runs only on the first render
const [rows, setRows] = useState(() => JSON.parse(localStorage.getItem('rows') ?? '[]'))

// Without the wrapper, JSON.parse runs on EVERY render and the
// result is thrown away every time except the first.
const [rows, setRows] = useState(JSON.parse(...))     // wasteful

// The initial value is used only on mount. Passing a changing prop
// as the initial value does not update it later — a frequent
// "why doesn't my component see the new prop" bug.
const [draft, setDraft] = useState(problem.title)     // frozen at mount

Updating Objects and Arrays

Always produce a new value. Mutating in place changes the data without telling React.

New object, new array, every time
// Objects
setUser({ ...user, name: 'Akshay' })
setUser(u => ({ ...u, prefs: { ...u.prefs, theme: 'dark' } }))   // nested

// Arrays — use the methods that return a new array
setRows(r => [...r, newRow])                       // add
setRows(r => r.filter(x => x.id !== id))           // remove
setRows(r => r.map(x => x.id === id ? { ...x, done: true } : x)) // update
setRows(r => [...r].sort(byDate))                  // sort a COPY

// These mutate and must not be used on state directly:
// push, pop, splice, sort, reverse
rows.push(newRow); setRows(rows)      // same reference -> no re-render

// Deeply nested state is a signal to flatten the shape or move to
// useReducer, not to write a five-level spread.

Key Points to Remember

  • 1State is a snapshot per render — reading it right after setting gives the previous value
  • 2Use the functional updater whenever the next value depends on the previous one
  • 3React batches updates in the same tick into a single re-render, including inside promises and timeouts
  • 4Pass a function to useState for expensive initial values, and remember the initial value is only used on mount
  • 5Never mutate state — push, splice and sort change the data without changing the reference React compares

Interview Questions

Sign in to ask Aria
1

Why does calling setCount(count + 1) twice in one handler only increment once?

Medium
2

What is the difference between useState(compute()) and useState(compute)?

Medium
3

Why does pushing to a state array and calling the setter not re-render?

Medium

Ask Aria about useState — The Core of Everything

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…