Vikas Tiwari

Writing

State as a snapshot - #React guide

7 min read
  • React
  • Learn React
  • JavaScript

State can feel like a normal variable, but each render gets a frozen picture of it. Here is why setState triggers new renders, why triple setNumber(number + 1) often only adds one, and why alerts and timeouts still “see” the old value—in plain English.

When you first learn React, state can feel like any other variable. You read it, you change it, and you expect the screen to follow right away. React works a little differently. Once you see the rule behind it, puzzles like “why did my counter only go up by one?” start to make sense.

Here is the short version. State does not change in the middle of your component run the way a normal let might. When you call something like setCount(5), you are not rewriting the count you have in this render. You are asking React to remember a new value and draw the UI again next time. So for each render, React gives you a frozen picture of state. That picture is what your JSX and your event handlers use until the next render.

Think of a Send button on a form. When the user submits, you might set a flag like isSent to true. The handler runs, setIsSent(true) schedules an update and a new render, and then React runs your component again with the new state. The screen updates because state changed and React re-rendered—not because the old UI morphed in place.

Rendering means React calls your function. The JSX you return is a snapshot of the UI at that moment: text, colors, what the button says—all based on state as it was for that call. React updates the real page to match that snapshot and wires up the event handlers from that same snapshot. When state changes, React calls your function again, you return a new snapshot, and the page updates again. State lives with React, not inside one function run. Each run gets a copy of the current values for that render.

A classic example is three calls to setNumber(number + 1) in one click. You might expect the number to rise by three, but it often only rises by one. During that render, number is fixed—say 0. Each line still reads 0, so you effectively ask for 1 three times, not 1 then 2 then 3. You can mentally replace number with its value for that render: setNumber(0 + 1) three times. After React finishes and re-renders, the next click uses number as 1, and so on. The handler closes over the snapshot from the render that created it.

The same idea shows up with alert(number) right after setNumber(number + 5). You often still see the old value in the alert, because number in that render did not change mid-handler. Even setTimeout(() => alert(number), 3000) uses the number from the render when you scheduled the timeout—not whatever is on screen three seconds later.

That behavior can protect you from subtle bugs. Imagine sending “Hello” to Alice, then changing the recipient to Bob before a delayed confirmation fires. With snapshots, the delayed code still sees the to and message from the render when you pressed Send—what you meant at click time—unless you deliberately choose a pattern that always reads the latest values.

Simple rules to remember: setState asks for a new render; during one render, state values are fixed for that render’s logic; each render gets its own snapshot of props and state; handlers created in an old render keep that render’s values across async gaps. When you need updates that build on the previous value in one event, use the updater function form like setN(n => n + 1), which React’s docs cover in the lesson on queueing a series of state updates.

In plain words: one render is one frozen picture of your data. Updates are new pictures, not edits to the old one. For official examples and exercises, see the React documentation on “State as a snapshot” at https://react.dev/learn/state-as-a-snapshot.