To update objects in React state, replace the object with a new copy instead of changing the existing one. Mutating an object does not trigger a render and corrupts the previous snapshot. The spread syntax is the usual way to build that copy, and it works the same for nested objects with one extra step.
Replace instead of mutate
Objects in state are meant to be treated as read-only. Changing a property directly mutates data from a previous render, and React never learns that a new value arrived. The safe habit is to always hand the setter a brand new object.
import { useState } from "react";
function Player() {
const [player, setPlayer] = useState({ name: "Ada", score: 0 });
function handleScore() {
player.score = player.score + 1;
}
return <button onClick={handleScore}>{player.name}: {player.score}</button>;
}Clicking the button changes nothing on screen. The score property was mutated, but the object is the same one from before, so React sees no change and skips the render.
React compares the old and new values with Object.is. Mutating in place leaves the reference unchanged, so the comparison reports no difference even though a property moved.
Treating state as immutable also keeps debugging clear, because an old log still shows the old object. It makes future features like undo and redo far easier to add, since past versions are never overwritten.
Copy with the spread syntax
Build a new object that copies the old fields and overrides the one you changed. The spread lists the old fields first, then the override wins, so pass the result to the setter returned by useState:
function handleScore() {
setPlayer({ ...player, score: player.score + 1 });
}Now the score climbs with each click. React receives a fresh object, compares it with the old one, sees they differ, and renders the updated score.
The spread copies every existing field first, then the new score wins. The same pattern works for any field, not just numbers.
Updating one field in a form
A form keeps all its fields in one object. Each change copies the object and overrides a single field, so a form with three fields stays in one variable instead of three.
function Form() {
const [person, setPerson] = useState({ first: "Ada", last: "Lovelace" });
function handleChange(event) {
setPerson({ ...person, first: event.target.value });
}
return <label>First name: <input value={person.first} onChange={handleChange} /></label>;
}Type in the input and the text updates as you go. Every keystroke replaces the person object with a new copy that keeps last unchanged and updates first.
For several fields, one handler can use the input name as the key, writing the setter with a computed property instead of a separate function per field. The same single handler then serves first, last, and email by reading the name attribute of the input that fired.
Updating a nested object
A nested field needs copies at every level above it. If person holds a nested profile, update it by copying both the outer and inner objects.
setPerson({
...person,
profile: { ...person.profile, city: "London" },
});The outer spread keeps name and the other fields, and the inner spread keeps the profile fields while changing only city. For anything deeper, flatten the state or use a library like Immer rather than stacking spreads.
The same replace-not-mutate rule applies to arrays, which are objects under the hood. Once you think in copies, both shapes use the same muscle memory.
Common mistakes
- Forgetting the spread deletes every other field when you replace the object.
- Mutating first and then calling the setter with the same object skips the render.
- Copying only the outer object still shares nested objects that other code may reference.
- Overwriting the object without the spread silently drops fields the next render still needs.
What to learn next
Group related fields in one object when they change together, such as the fields of one form. When several values are independent, separate state variables are often cleaner and simpler to update. Matching the shape of the state to how it updates removes most of the copying work, and flatter state is easier to copy.
Rune AI
Key Insights
- Never mutate an object that is already in state.
- Replace it with a new object passed to the setter.
- Spread copies existing fields, then override the changed one.
- Nested fields need copies at every level above them.
- Forgetting the spread deletes every other field.
Frequently Asked Questions
Why does mutating an object in state do nothing?
What does the spread syntax copy?
Is mutation ever allowed?
Conclusion
Treat objects in state as read-only and replace them with new copies. The spread syntax copies the old fields, and a nested field needs a copy at every level above it.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.