# Update nested state

Use immutable copies when an action changes a nested object, `Map`, or `Set`. For deeply nested objects, use the shipped Immer middleware to write the update with draft mutation syntax instead. The examples below render a counter or a collection summary in React, so you can see each update without losing sibling values.

## 1. Copy each level of a nested object

Create a store with [`create`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand#create) and put the update action beside its state. Use a functional `set` update to read the current value. Copy each object along the path to the changed field, keeping its other fields with object spread. You do not need to spread the top-level state: `set` merges the returned object at that level.

In your React TypeScript project, use this as `src/main.tsx`. The page starts at **Forest: 0 bears**. Click **Add bear** to increment the count; the title and unit stay visible.

```tsx title="src/main.tsx"
import { createRoot } from 'react-dom/client'
import { create } from 'zustand'

type CounterStore = {
  deep: {
    title: string
    nested: {
      unit: string
      obj: { count: number; label: string }
    }
  }
  increment: () => void
}

const useCounterStore = create<CounterStore>()((set) => ({
  deep: {
    title: 'Forest',
    nested: { unit: 'bears', obj: { count: 0, label: 'Add bear' } },
  },
  increment: () =>
    set((state) => ({
      deep: {
        ...state.deep,
        nested: {
          ...state.deep.nested,
          obj: {
            ...state.deep.nested.obj,
            count: state.deep.nested.obj.count + 1,
          },
        },
      },
    })),
}))

function App() {
  const deep = useCounterStore((state) => state.deep)
  const increment = useCounterStore((state) => state.increment)

  return (
    <main>
      <p>{deep.title}: {deep.nested.obj.count} {deep.nested.unit}</p>
      <button onClick={increment}>{deep.nested.obj.label}</button>
    </main>
  )
}

createRoot(document.getElementById('root')!).render(<App />)
```

![The counter starts at Forest: 0 bears, with an Add bear button below it.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/33b5eb3cecccc82609f3e2f486a7fa19.png)

The update creates new `deep`, `nested`, and `obj` objects. Each spread preserves sibling fields at that level; the top-level merge preserves the `increment` action. Copy the changed path rather than rebuilding unrelated parts of the store.

## 2. Use an Immer draft instead of nested spreads

Wrap the state creator in [`immer`](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/zustand-middleware-immer#immer) from `zustand/middleware/immer`. The middleware passes function updates through Immer's `produce`, so you can change a draft inside `set` while keeping the update immutable. It takes the state creator, not a configuration object.

Install Immer as a direct dependency alongside Zustand:

```bash
npm install zustand immer
```

Replace `src/main.tsx` with this alternative. It renders the same counter and preserves the same sibling values, but the action changes only the draft's count. Keep the extra parentheses in `create<CounterStore>()(...)` when using middleware with an explicit state type.

```tsx title="src/main.tsx"
import { createRoot } from 'react-dom/client'
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'

type CounterStore = {
  deep: {
    title: string
    nested: {
      unit: string
      obj: { count: number; label: string }
    }
  }
  increment: () => void
}

const useCounterStore = create<CounterStore>()(
  immer((set) => ({
    deep: {
      title: 'Forest',
      nested: { unit: 'bears', obj: { count: 0, label: 'Add bear' } },
    },
    increment: () =>
      set((state) => {
        state.deep.nested.obj.count += 1
      }),
  })),
)

function App() {
  const deep = useCounterStore((state) => state.deep)
  const increment = useCounterStore((state) => state.increment)

  return (
    <main>
      <p>{deep.title}: {deep.nested.obj.count} {deep.nested.unit}</p>
      <button onClick={increment}>{deep.nested.obj.label}</button>
    </main>
  )
}

createRoot(document.getElementById('root')!).render(<App />)
```

![The Immer counter shows the same Forest title, zero count, and Add bear button.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/33b5eb3cecccc82609f3e2f486a7fa19.png)

### Pitfall: class instances need Immer support

If your state contains class instances, mark them with `[immerable] = true` as described in [Immer's class documentation](https://immerjs.github.io/immer/complex-objects), and follow [Immer's rules](https://immerjs.github.io/immer/pitfalls). Without that marker, Immer mutates the instance without proxying it, which also changes the current state. If the current and next state are equal, Zustand skips subscriptions.

## 3. Copy mutable collections before changing them

For collections, create a new `Map` or `Set` from the current collection, then modify that copy. The following standalone alternative uses immutable copies without Immer. It starts with one bear and one selected animal. Click **Add bear** to increase the stored bear count, or **Select fox** to add another selected animal while keeping the bear selected.

```tsx title="src/main.tsx"
import { createRoot } from 'react-dom/client'
import { create } from 'zustand'

type CollectionStore = {
  counts: Map<string, number>
  selected: Set<string>
  addBear: () => void
  selectFox: () => void
}

const useCollectionStore = create<CollectionStore>()((set) => ({
  counts: new Map<string, number>([['bear', 1]]),
  selected: new Set<string>(['bear']),
  addBear: () =>
    set((state) => ({
      counts: new Map(state.counts).set(
        'bear',
        (state.counts.get('bear') ?? 0) + 1,
      ),
    })),
  selectFox: () =>
    set((state) => ({
      selected: new Set(state.selected).add('fox'),
    })),
}))

function App() {
  const counts = useCollectionStore((state) => state.counts)
  const selected = useCollectionStore((state) => state.selected)
  const addBear = useCollectionStore((state) => state.addBear)
  const selectFox = useCollectionStore((state) => state.selectFox)

  return (
    <main>
      <p>Bears: {counts.get('bear') ?? 0}</p>
      <p>Selected: {Array.from(selected).join(', ')}</p>
      <button onClick={addBear}>Add bear</button>
      <button onClick={selectFox}>Select fox</button>
    </main>
  )
}

createRoot(document.getElementById('root')!).render(<App />)
```

![The collection summary starts with one bear and bear selected, alongside Add bear and Select fox buttons.](https://atloriaassets.blob.core.windows.net/assets/a438221c-c1af-407c-b9e6-a56c986db846/d50c0f32e713db99050a20f8911000f1.png)

The changed collection gets a new reference, and existing entries survive the copy. To delete an entry, copy the collection into a local variable, call `delete` on the copy, and return it from the updater. Give collections explicit type arguments, as above, when initializing them empty too.

For the reference-equality rules behind these updates, see [State and actions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/state-and-actions). For selecting derived collection values, see [Selectors and subscriptions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/selectors-and-subscriptions).

## Live demos

- [Nested update approaches](https://stackblitz.com/edit/vitejs-vite-j6bjdygu)
- [Immer middleware](https://stackblitz.com/edit/vitejs-vite-3sgc4ejy)
- [Map and Set updates](https://stackblitz.com/edit/vitejs-vite-5cu5ddvx)

## Related

- [Update state with actions](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/update-state-with-actions)
- [Type stores and middleware](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/type-stores-and-middleware)
- [React quick start](https://bench-zustand-61.atloria.app/p/bench-zustand-61-pSJCWrWW5Y/developer/react-quick-start)
