Zustand updates state immutably, and its set function merges the update into the store one level deep.
Create a React-bound store with create, then update it through the set function supplied to the store initializer.
How the merge works
Pass only changed top-level fields to set: Zustand shallowly merges that partial object with the current state. A nested object is one field at the store level, so changing one of its properties requires creating a new object for that nested branch and copying the sibling fields you want to keep.
mermaidflowchart TD A["set({ profile: nextProfile })"] --> B["Shallow merge at store level"] B --> C["Other top-level fields stay in the store"] B --> D["profile is replaced by nextProfile"] D --> E["Copy profile and its changed nested branch"]
This React example mounts a store with flat, nested, Map, and Set state. Click the buttons to change the theme, increment the home visit count, and toggle whether the guide is selected. The screen shows each new value while retaining unrelated state.
tsximport { createRoot } from 'react-dom/client'
import { create } from 'zustand'
type State = {
profile: {
name: string
preferences: {
theme: string
density: string
}
}
visits: Map<string, number>
selected: Set<string>
changeTheme: () => void
incrementVisits: () => void
toggleGuide: () => void
}
const useStore = create<State>()((set) => ({
profile: {
name: 'Ada',
preferences: { theme: 'light', density: 'comfortable' },
},
visits: new Map<string, number>([['home', 0]]),
selected: new Set<string>(['guide']),
changeTheme: () =>
set((state) => ({
profile: {
...state.profile,
preferences: {
...state.profile.preferences,
theme: state.profile.preferences.theme === 'light' ? 'dark' : 'light',
},
},
})),
incrementVisits: () =>
set((state) => ({
visits: new Map(state.visits).set(
'home',
(state.visits.get('home') ?? 0) + 1,
),
})),
toggleGuide: () =>
set((state) => {
const selected = new Set(state.selected)
if (selected.has('guide')) {
selected.delete('guide')
} else {
selected.add('guide')
}
return { selected }
}),
}))
function App() {
const name = useStore((state) => state.profile.name)
const theme = useStore((state) => state.profile.preferences.theme)
const density = useStore((state) => state.profile.preferences.density)
const visits = useStore((state) => state.visits)
const guideSelected = useStore((state) => state.selected.has('guide'))
const changeTheme = useStore((state) => state.changeTheme)
const incrementVisits = useStore((state) => state.incrementVisits)
const toggleGuide = useStore((state) => state.toggleGuide)
return (
<main>
<h1>{name}'s preferences</h1>
<p>Theme: {theme}; density: {density}</p>
<button onClick={changeTheme}>Change theme</button>
<p>Home visits: {visits.get('home')}</p>
<button onClick={incrementVisits}>Visit home</button>
<p>Guide selected: {guideSelected ? 'yes' : 'no'}</p>
<button onClick={toggleGuide}>Toggle guide</button>
</main>
)
}
const container = document.getElementById('root')!
createRoot(container).render(<App />)
At first, the page shows Ada's light, comfortable preferences, zero home visits, and the guide as selected. Changing the theme preserves the density; each visit increments the displayed count; toggling the guide changes its displayed selection without changing the other values.
Collections need new instances
Map and Set are mutable: calling set, add, or delete changes a collection without changing its reference. Make a copy, change the copy, and put that new instance in the store, as the example does. This gives a component selecting the collection or one of its values a changed reference to observe. See the Map and Set demo for more collection examples.
When you use Immer
For nested updates, the immer middleware lets an update recipe use mutation syntax while producing an immutable update. If you use Immer with class objects, mark them with [immerable] = true; without that marker Immer can mutate the current state without a proxy, and Zustand can skip notifying subscriptions.
Related
- Update nested state for nested object update patterns.
- Select state efficiently for selectors and avoiding unnecessary re-renders.
- React quick start for the separate issue of mutating an object from a previous render.
Was this page helpful?