# 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`](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/zustand#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:

```tsx
import { 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>,
  )
}
```

![The mounted inventory shows stock quantities, selection state, and update controls.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/7487d5a8666c6c460b1427cfa205aa23.png)

`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:

```ts
import { 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:

```ts
const ids = new Set([] as string[])
const users = new Map([] as [string, { name: string }][])
```

## See it running

Open the [Map and Set demo](https://stackblitz.com/edit/vitejs-vite-5cu5ddvx) to see collection updates in a mounted app.

## Related

- [Immutable state updates](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/immutable-state-updates)
- [Selectors and rendering](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/selectors-and-rendering)
- [Update nested state with Immer](https://bench-zustand-56.atloria.app/p/bench-zustand-56-D8Xr4XerWj/developer/update-nested-state-with-immer)
