What Actually Happens After You Call setState in React?

setState requests work; it does not rewrite a variable

A state setter queues an update for React. The variable in the currently running handler belongs to the current render, so it does not change midway through that handler. This is why logging immediately after a setter prints the old value.

React can group several updates from the same interaction. That batching avoids rendering a halfway state where one update has landed and another has not.

function save() {
  setStatus("saving");
  console.log(status); // status from this render
  setAttempts(current => current + 1);
}

Render is calculation, commit is visible change

During render, React calls components to calculate a candidate tree. It may pause, discard, or retry this work. During commit, React applies the chosen changes to the DOM. Effects run after React commits, which makes effects appropriate for synchronizing with systems outside React, not for calculating UI values.

That distinction explains a common error: doing analytics, focus, or network work directly in render can run it more than once without a visible UI change.

  • Render: derive what the UI should look like.
  • Commit: update DOM, refs, and layout effects.
  • After paint: run ordinary effects where possible.

Why functional updates are composable

When two updates depend on the previous value, pass updater functions. React can apply them in order to the latest queued state, even when batching changes when React renders.

setScore(score + 1);
setScore(score + 1); // often results in one increment

setScore(previousScore => previousScore + 1);
setScore(previousScore => previousScore + 1); // always two increments

Use transitions for work that can wait

Typing is urgent; filtering a huge result list can be interruptible. startTransition tells React that the second update is non-urgent. It is a scheduling hint, not a loading library and not a way to make an API request faster.

The practical outcome is responsive input while React works on a later view. Keep the input's own state outside the transition and place the expensive view update inside it.

setSearchText(nextText);
startTransition(() => {
  setSearchQuery(nextText);
});

Key Takeaways

  • State belongs to a render snapshot; setters schedule the next snapshot.
  • Never rely on a setter changing a local variable immediately.
  • Separate urgent interaction updates from interruptible visual work with transitions.

Ready to explain this under interview pressure?

Learn the mental models, then practise applying them to realistic frontend problems.

Join Cohort 3 Waitlist
Back to Articles