In React, state must be treated as immutable — you should never modify it directly. Instead, always create a new copy of the state, update that copy, and pass it to the setter function (like setState or setSomething).
map(), filter(), and concat() for safe updates.React.memo and PureComponent.Common immutable update patterns in React:
setUser(prev => ({ ...prev, age: prev.age + 1 }))setTasks(prev => [...prev, "New task"])setTasks(prev => prev.filter(t => t.id !== id))setUser(prev => ({ ...prev, address: { ...prev.address, city: "Mumbai" } }))These patterns always return a new reference, which lets React know that something changed and triggers a re-render.
Directly changing a state object (like user.age = 25) mutates the existing object instead of creating a new one. React does not detect this change reliably, so your UI may not update.
// React component that mutates state directly (not recommended)
function Example() {
const [user, setUser] = React.useState({ name: "Aman", age: 20 });
const handleClick = () => {
user.age = 25; // ❌ Direct mutation of the existing state object
console.log(user); // Logs updated object, but React may not re-render the UI
};
return (
<div>
<p>Age: {user.age}</p>
<button onClick={handleClick}>Update</button>
</div>
);
}
React compares the previous and next state values by reference. Here, the user object reference stays the same (only its inner value changes), so React assumes nothing changed and skips re-rendering. The console shows the new age, but the displayed UI may still show the old value.
The correct way is to use the setter function with a new state object. The spread operator ...prev copies existing properties into a fresh object, and then you override only what changed.
// React component that updates state immutably using a new object
function Example() {
const [user, setUser] = React.useState({ name: "Aman", age: 20 });
const handleClick = () => {
setUser(prev => ({ ...prev, age: prev.age + 1 })); // ✅ New object with updated age
};
return (
<div>
<p>Age: {user.age}</p>
<button onClick={handleClick}>Increase Age</button>
</div>
);
}
Every time you click Increase Age, React receives a new user object. The reference changes, so React re-renders the component and shows the updated age on the screen.
Arrays also must be updated by creating new arrays, not by mutating the existing one.
// Todo list that adds items using an immutable array update
function TodoList() {
const [tasks, setTasks] = React.useState(["Learn React", "Do homework"]);
const addTask = () => {
setTasks(prev => [...prev, "Practice coding"]); // ✅ New array using spread
};
return (
<div>
<ul>
{tasks.map((t, i) => <li key={i}>{t}</li>)}
</ul>
<button onClick={addTask}>Add Task</button>
</div>
);
}
Methods like push(), pop(), and splice() mutate the original array. Instead, use [...prev, item], concat(), filter(), or map() to return a brand-new array so React can detect the change.
For nested objects, copy each level that you update. This is still a shallow copy at each level, but combined it behaves like a safe deep update for the fields you care about.
// Updating a nested address field without mutating original objects
function DeepExample() {
const [user, setUser] = React.useState({
name: "Riya",
address: { city: "Pune", pincode: 411001 },
});
const updateCity = () => {
setUser(prev => ({
...prev,
address: { ...prev.address, city: "Mumbai" }, // ✅ Copy address and update only city
}));
};
return (
<div>
<p>City: {user.address.city}</p>
<button onClick={updateCity}>Change City</button>
</div>
);
}
Only the city field changes, but we still create a new address object and a new user object. This preserves immutability and makes sure React re-renders with the new city.
React.memo and PureComponent.push(), pop(), or splice() directly on state arrays.user.address.city = "X").state or props....) or methods like map(), filter(), and concat() to update data.setState.Immer to simplify immutable updates.count++) — observe that React may not re-render as expected.setCount(count + 1) or setCount(prev => prev + 1).filter() and array spread / concat().Goal: Understand why React enforces immutability and learn how to update arrays and objects safely without breaking state reactivity.