React Updating State

In the last lesson you learned the React useState Hook, so you can store a value and get a setter. This lesson clears up the surprise that trips everyone: when you call the setter, the value does not change right away.

🤔 Why state feels confusing at first

Calling the setter looks like a normal assignment, but it does not behave like one.

  • You write setCount(count + 1) and expect count to change immediately. It does not.
  • React does not update the variable on that line. It just makes a note: “please re-render with the new value”.
  • The actual change shows up on the next render, not on the next line of code.
  • So the old value sticks around for the rest of the current function run.

That little gap between “I called the setter” and “the value actually changed” is where most state bugs come from. The fix is to understand when the update really happens.

⏳ State updates are asynchronous

When you call the setter, React does not change the value instantly. The update is asynchronous, which just means it happens a little later, not on the same line.

  • You call setCount(count + 1). React schedules a re-render.
  • The count variable in the running function stays the same until that re-render.
  • So if you read count right after setting it, you get the old value. Every time.

This counter reads the state right after setting it, so watch what the log prints.

function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
console.log(count); // still the OLD value, not the new one
}
return <button onClick={handleClick}>Count: {count}</button>;
}

So here is what happens on that click:

  • When count is 0 and you click, the log prints 0, not 1.
  • The screen does update to 1 on the next render.
  • But the console.log on that line still saw the old value.

Output

0

Why React does this

React waits and groups updates together so it can re-render once instead of many times. That makes your app fast. The trade-off is that the value does not change on the same line you set it.

📦 Updates are batched

There’s a second part to this. React does not just delay one update. It batches them, which means it collects several setter calls in the same event and applies them together.

  • Inside one click handler, all your setter calls get grouped.
  • They all read from the same old value, because the re-render hasn’t happened yet.
  • Then React re-renders once at the end with the final result.

This handler calls the setter three times in a row, so you’d think the count jumps by three.

function handleClick() {
setCount(count + 1); // count is 0, so this means setCount(1)
setCount(count + 1); // count is STILL 0, so this means setCount(1)
setCount(count + 1); // count is STILL 0, so this means setCount(1)
}

So watch what these three lines actually do:

  • All three lines read count as 0.
  • Each one says “set it to 1”.
  • The count goes from 0 to 1, not to 3.

This is the classic bug, and the next section fixes it.

🔁 The functional updater form

When the new state depends on the old state, don’t pass a plain value. Pass a function instead. This is the functional updater form, and React hands it the latest value.

  • You write setCount(prev => prev + 1).
  • prev is the most recent state value, even mid-batch.
  • Each call builds on the result of the one before it, so they stack up correctly.

Here are both forms side by side. The broken one reads a stale value three times. The safe one builds on prev each time.

// ❌ Wrong - all three read the same old count, adds only 1
function addThreeBroken() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
// ✅ Right - each call gets the latest value, adds 3
function addThreeFixed() {
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
}

So see the difference when you click once:

  • With the broken version, the count moves by 1.
  • With the fixed version, the same click moves it by 3.
  • That happens because prev carries the running result forward.

A simple rule

If the new state is built from the old state, use the function form: setCount(prev => prev + 1). If you’re setting a fresh value that doesn’t depend on the old one, like setName("Riya"), the plain value is fine.

🚫 Never change state directly

One more rule that matters a lot. Never change the state variable yourself. Always go through the setter.

  • Writing count = count + 1 does nothing useful. React has no idea you changed it.
  • Without the setter, React never schedules a re-render, so the screen never updates.
  • The setter is the only thing that tells React “the state changed, please show the new value”.

This shows the mistake next to the correct call.

function handleClick() {
// ❌ Wrong - React doesn't know, the screen never updates
count = count + 1;
// ✅ Right - the setter tells React to re-render
setCount(prev => prev + 1);
}

So changing count by hand is invisible to React. Only the setter starts the re-render that puts the new value on screen.

The setter is the only door

Think of state as a locked box. The setter is the only key. If you try to change the value without it, React never notices, and your UI stays stuck on the old number.

🧩 What You’ve Learned

  • ✅ Calling the setter is asynchronous, so the value does not change on the same line
  • ✅ Reading state right after setting it gives you the old value, not the new one
  • ✅ React batches setter calls in one event and re-renders once at the end
  • ✅ Calling setCount(count + 1) three times only adds 1, because all three read the same old value
  • ✅ The functional updater setCount(prev => prev + 1) gives you the latest value, so three calls add 3
  • ✅ Use the function form whenever the new state depends on the old state
  • ✅ Never change state directly, always go through the setter so React re-renders

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    Why does reading state right after calling the setter show the old value?

    Why: The setter schedules a re-render instead of changing the variable instantly. The running function keeps the old value until that next render.

  2. 2

    What happens if you call setCount(count + 1) three times in a row when count is 0?

    Why: All three calls read count as 0 because the re-render hasn't happened yet. Each one sets it to 1, so the final value is 1, not 3.

  3. 3

    Which form safely adds 3 when called three times in one handler?

    Why: The functional updater gets the latest value as prev, even mid-batch. Each call builds on the one before, so three calls add 3.

  4. 4

    Why should you never write count = count + 1 to update state?

    Why: Changing the variable by hand is invisible to React. Only the setter schedules a re-render that puts the new value on screen.

🚀 What’s Next?

Now you can update a number safely. But real apps store more than single values, like user profiles and form data held in objects, and those need a bit of extra care when you update them.

React State with Objects

Share & Connect