React Mastery / React Hooks

useState with Previous State

The useState hook with previous state is used when the new state depends on the current value.

Since React state updates are asynchronous and can be batched, using the previous state ensures the update is always accurate, especially when multiple updates happen quickly.


❓ Why update based on previous state?

When you call setState with a new value like setState(state + 1), React schedules an update. But if you call setState multiple times synchronously (e.g., in a loop or multiple event calls), all these calls may reference the same stale initial state value, ignoring intermediate updates.

Example (Problematic):

for (let i = 0; i < 5; i++) {
  setCount(count + 1); *// 'count' does not update in this loop iteration*
}

This results in the counter increasing by only 1, not 5, because every loop uses the same stale count.


❌ Problem Example: Stale State

import React, { useState } from "react";

function Counter() {
const [count, setCount] = useState(0);

const increaseWrong = () => {
	setCount(count +1);
	setCount(count +1);
	setCount(count +1);
};

return (
	<div>
		<p>Count: {count}</p>
		<button onClick={increaseWrong}>Increase by 3 (Wrong)</button>
	</div>
	);
}

export default Counter;

❓ What You Expect

  • Count should increase by 3

❌ What Actually Happens

  • Count increases by only 1

❗ Why This Happens

  • All three setCount(count + 1) calls use the same old count value

  • React batches these updates and ignores intermediate results

  • Each update overwrites the previous one


✅ Solution: Functional Update (Previous State)

React provides a functional updater form of setState:

setState(prevState => newState);
  • prevState is the latest available state

  • React guarantees this value is correct, even in batched updates


✅ Correct Example: Using Previous State

import React, { useState } from "react";

function Counter() {
const [count, setCount] =useState(0);

constincreaseCorrect = () => {
	setCount(prevCount => prevCount +1);
	setCount(prevCount => prevCount +1);
	setCount(prevCount => prevCount +1);
};

return (
	<div>
		<p>Count: {count}</p>
		<button onClick={increaseCorrect}>Increase by 3 (Correct)</button>
	</div>
 );
}

export default Counter;

✅ What Happens Now

  • Each update receives the latest updated value

  • Final count increases correctly by 3



🔹 How React Handles This Internally (Conceptually)

Initial count = 0

Update 1 → prevCount = 0 → new count = 1
Update 2 → prevCount = 1 → new count = 2
Update 3 → prevCount = 2 → new count = 3

Each update runs sequentially, even though React batches them.


🔹 When You MUST Use Previous State

Use the functional update form when:

  • New state depends on old state

  • Multiple setState calls exist in one function

  • Working with:

    • Counters

    • Toggles

    • Accumulators

    • Timers

    • Async updates


🔹 Common Safe Patterns

// Increment
setCount(prev => prev +1);

// Decrement
setCount(prev => prev -1);

// Toggle
setIsOpen(prev => !prev);

🔹 When You DON’T Need Previous State

// Direct assignment is fine
setCount(0);
setName("React");

Use this only when the new value does not depend on the old one.