React Updating State
Table of Contents + −
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 expectcountto 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
countvariable in the running function stays the same until that re-render. - So if you read
countright 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
countis0and you click, the log prints0, not1. - The screen does update to
1on the next render. - But the
console.logon that line still saw the old value.
Output
0Why 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
countas0. - Each one says “set it to
1”. - The count goes from
0to1, not to3.
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). previs 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 1function addThreeBroken() { setCount(count + 1); setCount(count + 1); setCount(count + 1);}
// ✅ Right - each call gets the latest value, adds 3function 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
prevcarries 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 + 1does 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
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
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
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
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.