Stop Writing Your React State Updates Like Regular JavaScript. | by Shreyash | Jul, 2026
The Perfect Counter Trap
Imagine you are building a simple game dashboard. You have a button that is supposed to give a player a massive “Triple Bonus” boost. When clicked, it should add 3 points to their current score all at once.
Naturally, you write a function that looks like this:
const [score, setScore] = useState(0);const handleTripleBonus = () => {
setScore(score + 1);
setScore(score + 1);
setScore(score + 1);
};
It looks incredibly straightforward. If the current score is 0, line 4 makes it 1, line 5 makes it 2, and line 6 pushes it to 3.
You save your code, open your browser, and hit the button.
Instead of jumping to 3, the score smoothly transitions from 0 to….. 1. You click it again. It goes to 2. No matter how many times you bundle those three state updates together, React acts as if you only wrote a single line of code.
Why React Does This?
To understand why this happens, you have to realize a fundamental truth about React: state updates are not instantaneous. They take some time to happen as they are scheduled by React and we essentially re-execute the component to get the updated state snapshot ONLY once the current component execution finishes.
When you call setScore(), you aren’t changing the variable right then and there. Instead, you are submitting a request to React, saying, “Hey, the next time you get a chance to re-render the screen, please update this value.”
Get Shreyash’s stories in your inbox
Join Medium for free to get updates from this writer.
Furthermore, React uses a concept called Snapshots. When your component renders, it takes a frozen snapshot of the state at that exact microsecond. For that entire execution of the component function, the value of score is locked in.
This behavior is called Batching. It is actually a massive feature, not a bug. If React re-rendered the entire UI every single time a state hook was touched, a complex application with dozens of rapid state updates would lag and stutter. React waits for your event handler function to finish completely before compiling the changes and drawing the new layout all at once.
The Fix: Functional State Updates
So, how do you tell React that you want to calculate values based on the most recent state value, rather than the old state snapshot value?
Instead of passing a raw value like score + 1 into your setter function, you pass a callback function.
JavaScript
const handleTripleBonus = () => {
setScore(prevScore => prevScore + 1);
setScore(prevScore => prevScore + 1);
setScore(prevScore => prevScore + 1);
};
When you pass a function inside setScore, you are telling React: “I don’t know what the score is right now, but whatever the absolute latest value is in your queue, take it, call it prevScore, and add 1 to it.”
Now, React processes the updates like a real conveyor belt:
- It takes the initial snapshot (
0), adds 1 , pending value is now 1. - It looks at the pending queue, sees the latest value is 1, passes it as
prevScore, adds 1 , pending value is now 2. - It takes the latest value of 2, passes it into the final function, adds 1. Final value is 3.
When the function finishes, React executes a single re-render, and your UI instantly jumps to 3 exactly like you originally intended.
The Mental Shift
Transitioning from regular JavaScript to React requires a complete mental shift in how you view data. In standard programming, variables change dynamically on the fly. In React, variables are fixed pictures of a specific moment in time.
The next time you need to update a piece of state and the new value relies directly on what the old value was, don’t use the snapshot variable. Always use the functional updater (prev => prev + 1) to ensure you are working with the latest state snapshot!