React State · Safe Updates · Immutability
Updating state in React must be done carefully to ensure predictable and stable component behavior. State updates are asynchronous and should always be treated as immutable.
In this lesson you will learn how to safely update primitive, object, and array state using React's recommended patterns like setter functions, functional updates, and batched updates.
useState setter function instead of modifying variables directly....) before updating them.In functional components, the correct way to update state is by using the setter function returned by useState.
// Basic counter using the state setter function
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const increment = () => {
// ✅ Correct: use the setter instead of mutating count directly
setCount(count + 1);
};
return (
<div>
<h3>Count: {count}</h3>
<button onClick={increment}>Increment</button>
</div>
);
}
Never modify state variables directly, for example using count++ or count = count + 1. Always go through the setter (like setCount) so React can schedule the update and re-render correctly.
When your new state depends on the old state, use the functional update form of the setter. React will pass the latest value to your updater function.
// Use a functional update when new state depends on previous state
setCount(prevCount => prevCount + 1);
This ensures the correct previous value is used, especially when multiple updates happen quickly or in async events.
// Counter that safely increments twice using the previous state pattern
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const incrementTwice = () => {
// Each update receives the latest count value
setCount(prev => prev + 1);
setCount(prev => prev + 1);
};
return (
<div>
<h3>Count: {count}</h3>
<button onClick={incrementTwice}>Increment Twice</button>
</div>
);
}
When you click Increment Twice, React will call your updater twice. Because each call receives the latest value of count, the final result is +2, not +1.
State in React is immutable — you should always create a new copy before updating objects or arrays. A common pattern is to use the spread operator (...).
// Safely updating an object state using the spread operator
import { useState } from "react";
function Profile() {
const [user, setUser] = useState({ name: "Aman", age: 20 });
const increaseAge = () => {
// ✅ Copy the previous object, then override the age field
setUser(prev => ({ ...prev, age: prev.age + 1 }));
};
return (
<div>
<h4>{user.name} - {user.age}</h4>
<button onClick={increaseAge}>Grow Older</button>
</div>
);
}
When increaseAge runs, React creates a new user object by copying all previous fields with {`{ ...prev }`} and then updating age. This avoids mutating the existing state and keeps React's change detection reliable.
React automatically batches multiple state updates made during the same event into a single re-render. You can safely update multiple pieces of state inside one handler.
// Multiple state values updated together in a single event
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState("Guest");
const updateBoth = () => {
// React batches these updates into one re-render
setCount(prev => prev + 1);
setName("User");
};
return (
<div>
<p>{name} clicked {count} times.</p>
<button onClick={updateBoth}>Update</button>
</div>
);
}
Clicking Update changes both name and count. React batches these changes so the component re-renders once, which is more efficient and avoids intermediate visual states.
prev => ...) whenever new state depends on the old value.state.value = newValue.useReducer.{`{ name, score }`}) and safely update one key at a time.useState hooks in one component and update them together to observe batching.[...prevTodos, newTodo] and verify immutability.Goal: Learn how to update React state safely using functional updates, immutability, and batching principles for consistent and predictable behavior.