Updating Nested State and Arrays
In React and Zustand, state updates must be immutable. You must never mutate the existing state directly. Instead, you must return a shallow copy of the state containing the updated properties.
1. Updating Array Lists
To modify arrays in your store, use standard JavaScript immutable methods like the spread operator ..., .map(), or .filter().
A. Appending an Item
addTodo: (newText) => set((state) => ({
todos: [...state.todos, { id: Date.now(), text: newText, done: false }]
}))B. Removing an Item
removeTodo: (todoId) => set((state) => ({
todos: state.todos.filter((todo) => todo.id !== todoId)
}))C. Modifying an Item Properties
toggleTodo: (todoId) => set((state) => ({
todos: state.todos.map((todo) =>
todo.id === todoId ? { ...todo, done: !todo.done } : todo
)
}))2. Modifying Nested Objects
Updating deeply nested objects manually using spread operators can lead to long, hard-to-read lines:
// Messy nested state copy
updateAddress: (newCity) => set((state) => ({
user: {
...state.user,
profile: {
...state.user.profile,
address: {
...state.user.profile.address,
city: newCity
}
}
}
}))3. Simplifying Updates using Immer
To avoid nesting copy hell, you can integrate the Immer library. Immer allows you to write mutation-like code inside a draft block, and it compiles that code into safe immutable updates automatically.
First, install the immer utility package:
npm install immerNext, import and wrap your state updates inside the immer helper:
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
interface UserStore {
user: {
name: string;
profile: {
city: string;
};
};
updateCity: (city: string) => void;
}
export const useUserStore = create<UserStore>()(
immer((set) => ({
user: {
name: "John",
profile: {
city: "Boston",
},
},
// Write mutation-like updates safely on the draft parameter
updateCity: (city) =>
set((state) => {
state.user.profile.city = city;
}),
}))
);Published on Last updated: