To update nested state with a reducer, copy every object from the changed field up to the top. The reducer returns a fresh object at each level instead of mutating the old one. Deep clones are not required; a shallow spread at each touched level is enough.
Getting the copies right prevents silent update bugs, where the user types a value and the screen never reflects it.
Why shallow copies are not enough
The spread operator copies only one level. If you spread the top object but leave a nested object untouched, that nested object still points at the previous render's data. React compares references, so an unchanged nested reference looks like no update.
A nested object is really a separate object, so updating it means building a new child and pointing the new parent at it.
function reducer(person, action) {
switch (action.type) {
case "changed_city":
return {
...person,
address: { ...person.address, city: action.city }
};
default:
return person;
}
}The changed_city case copies person, then copies address and overrides city. Both levels get new references, so React can see that the state changed. See how to update objects in React state for the same rule outside reducers.
Update an array nested in an object
When the nested value is an array, replace it with map or filter. Map returns a new array, and each changed item gets its own new object.
function reducer(state, action) {
switch (action.type) {
case "toggled":
return {
...state,
tasks: state.tasks.map((task) => (task.id === action.id ? { ...task, done: !task.done } : task))
};
default:
return state;
}
}The toggled case spreads state, then maps tasks into a new array. The matching task is copied with its done flag flipped, while the others keep their references. Nothing below the change is mutated, and each list item still needs a stable key when it is rendered.
Flatten when the nesting hurts
Deep nesting makes every update copy more levels. Before reaching for Immer, ask whether the nested data really needs to be nested. A flat shape, such as a separate task array instead of tasks inside a user object, updates with a single spread.
Use Immer when copying gets noisy
For deeply nested state, Immer lets you write mutating style while it produces the copies for you. Its useImmerReducer hook takes a reducer that can mutate a draft.
import { useImmerReducer } from "use-immer";
function reducer(draft, action) {
switch (action.type) {
case "changed_city":
draft.address.city = action.city;
break;
default:
break;
}
}The draft is a safe copy that records your edits, so the reducer never mutates real state. Immer figures out which parts of the draft changed and builds the new object for you, so the code reads like mutation without being one.
Immer is a peer dependency, so install both with npm install immer use-immer, then swap useReducer for useImmerReducer. Read how to write actions and reducers for the purity rules Immer is respecting for you.
Rune AI
Key Insights
- Spread copies only one level.
- Copy each object on the path to the change.
- Map and filter replace arrays immutably.
- Immer lets you write mutating style safely.
- Consider flattening deeply nested state.
Frequently Asked Questions
Why is the spread operator not enough for nested state?
Do I need to deep clone the whole state?
What if my state gets deeply nested?
Conclusion
Updating nested state in a reducer means copying every object from the changed field up to the root. Spread at each touched level, replace arrays with map and filter, and reach for Immer when the copying becomes repetitive.
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.