Appesto Software Solutions
React

Why useState doesn’t update immediately (and what to do instead)

July 30, 2026 · 2 min read ·

Every React developer writes this code once: call setCount(count + 1), console.log(count) on the next line, and see the old value. It looks broken. It isn't – and understanding why unlocks half of React's mental model.

State is a snapshot, not a variable

const [count, setCount] = useState(0);

function handleClick() {
  setCount(count + 1); // schedules a re-render with 1
  console.log(count);  // still 0 - THIS render's snapshot
  setCount(count + 1); // ❌ still 0 + 1 = 1, not 2!
}

setState doesn't mutate the variable – it schedules a re-render, and the new value only exists in that next render. Inside the current function, `count` is a constant captured when the render started. That's also why calling setCount(count + 1) twice increments once: both calls read the same snapshot.

Fix 1: functional updates for sequential changes

setCount((c) => c + 1);
setCount((c) => c + 1); // ✅ now it's 2 - each updater gets the latest value

Fix 2: don't wait for state you already have

The most common real-world version of this question is 'how do I use the new state right after setting it?' – usually to fire a request. The answer: you already have the value. You just computed it. Use the local variable instead of round-tripping through state.

function handleSelect(id: string) {
  setSelectedId(id);
  fetchDetails(id);      // ✅ use the value directly
  // not: fetchDetails(selectedId) - that's the OLD one
}

Fix 3: useEffect when the update comes from elsewhere

When you genuinely need to react to a state change you don't control locally – a value set by a child, a store, or a URL param – an effect with that value in its dependency array is the correct tool. Reserve it for that case; an effect that just mirrors a handler you own is extra indirection.

Bonus: React 18+ batches everything

Since React 18, multiple setState calls in the same tick – including inside promises, timeouts, and native event handlers – are batched into one re-render. That's a performance win, but it means you can never rely on a render happening 'between' two updates. Design handlers around snapshots and functional updates and batching becomes invisible.

Setting state is a request for the next render, not an edit to the current one. Once that clicks, this entire class of bug disappears.

Keep reading

Related articles