Updating a number or string in state is straightforward — setCount(count + 1). Objects and arrays need one more rule: React state must never be mutated directly.
React decides whether to re-render partly by checking if the state value is a different object than before. Mutating the existing object in place keeps it the exact same object reference — React can't tell anything changed, and silently skips the re-render:
function Profile() {
const [user, setUser] = useState({ name: 'Priya', age: 21 });
function haveBirthday() {
user.age = user.age + 1; // mutates the existing object — DOES NOT re-render
setUser(user); // same reference as before, React sees no change
}
return <button onClick={haveBirthday}>{user.name} is {user.age}</button>;
}Create a new object (or array) with the change applied, using the spread operator from the JavaScript ES6 lesson, and pass that new object to the setter:
function haveBirthday() {
setUser({ ...user, age: user.age + 1 }); // a new object, one field changed
}{ ...user, age: user.age + 1 } copies every field from user into a brand-new object, then overwrites age — a genuinely new reference React can compare against the old one.
| Task | Don't | Do |
|---|---|---|
| Add an item | items.push(newItem) | setItems([...items, newItem]) |
| Remove an item | items.splice(i, 1) | setItems(items.filter((_, idx) => idx !== i)) |
| Update an item | items[i].done = true | setItems(items.map((item, idx) => idx === i ? { ...item, done: true } : item)) |
function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: 'Learn React', done: false },
]);
function toggleTodo(id) {
setTodos(todos.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
}
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} onClick={() => toggleTodo(todo.id)}>
{todo.done ? '✓' : '○'} {todo.text}
</li>
))}
</ul>
);
}Non-mutating vs. mutating array methods
Array methods split cleanly into two groups: map(), filter(), and the spread operator ([...arr]) all return a new array, safe for state. push(), splice(), sort(), and direct index assignment (arr[0] = x) all mutate the existing array — never use these directly on state.