Update Maps and Sets immutably
Use this when a Zustand state field is a Map or Set and an update must appear in subscribed React components. Create a new collection for each update; do not mutate the collection already in the store.
Use new references for collection updates
create returns a hook that reads selected state and exposes the actions in the same store. Define the collection types explicitly, then copy the existing collection before changing the copy:
tsximport { create } from 'zustand'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
type InventoryStore = {
stock: Map<string, number>
selected: Set<string>
receive: (sku: string, quantity: number) => void
toggleSelected: (sku: string) => void
removeSku: (sku: string) => void
}
const useInventoryStore = create<InventoryStore>((set) => ({
stock: new Map([
['apple', 12],
['orange', 8],
]),
selected: new Set(['apple']),
receive: (sku, quantity) =>
set((state) => ({
stock: new Map(state.stock).set(
sku,
(state.stock.get(sku) ?? 0) + quantity,
),
})),
toggleSelected: (sku) =>
set((state) => {
const next = new Set(state.selected)
if (next.has(sku)) {
next.delete(sku)
} else {
next.add(sku)
}
return { selected: next }
}),
removeSku: (sku) =>
set((state) => {
const stock = new Map(state.stock)
stock.delete(sku)
const selected = new Set(state.selected)
selected.delete(sku)
return { stock, selected }
}),
}))
export function Inventory() {
const stock = useInventoryStore((state) => state.stock)
const selected = useInventoryStore((state) => state.selected)
const receive = useInventoryStore((state) => state.receive)
const toggleSelected = useInventoryStore((state) => state.toggleSelected)
const removeSku = useInventoryStore((state) => state.removeSku)
return (
<section>
<h2>Inventory</h2>
<ul>
{Array.from(stock, ([sku, quantity]) => (
<li key={sku}>
{sku}: {quantity}{' '}
<button type="button" onClick={() => receive(sku, 1)}>
Receive one
</button>{' '}
<button type="button" onClick={() => toggleSelected(sku)}>
{selected.has(sku) ? 'Deselect' : 'Select'}
</button>{' '}
<button type="button" onClick={() => removeSku(sku)}>
Remove
</button>
</li>
))}
</ul>
<p>Selected: {Array.from(selected).join(', ') || 'none'}</p>
</section>
)
}
const root = document.getElementById('root')
if (root) {
createRoot(root).render(
<StrictMode>
<Inventory />
</StrictMode>,
)
}
receive creates a new Map, changes one entry, and stores that reference. toggleSelected and removeSku create new Set and Map instances before changing them. The mounted Inventory component therefore shows the changed quantity, selection text, or list after each button click.
The updater returns only the changed fields because Zustand's set operation merges the returned state at one level. A Map or Set is a nested value, so replacing that field with a new instance is the immutable update. For a delete, copy first, call delete, and return the copy. To clear a collection, return new Map() or new Set() for that field.
Avoid in-place mutation
Do not return the same collection reference after changing it:
tsimport { create } from 'zustand'
const useInventoryStore = create<{
stock: Map<string, number>
mutateInPlace: () => void
}>((set) => ({
stock: new Map([['apple', 12]]),
mutateInPlace: () =>
set((state) => {
state.stock.set('apple', 13)
return { stock: state.stock }
}),
}))
That update mutates the existing Map and returns its existing reference. Zustand compares the next state with the current state using Object.is; a collection update must provide a new reference for the subscribed value to change.
Type empty collections explicitly
When a collection starts empty, give its element types in the store type or in the initializer. The store type in the example above does this. If you infer from an empty array directly, TypeScript can infer never[], which prevents later additions. These initializers preserve the intended types:
tsconst ids = new Set([] as string[])
const users = new Map([] as [string, { name: string }][])
See it running
Open the Map and Set demo to see collection updates in a mounted app.
Was this page helpful?