# Immutable state updates

Zustand updates state immutably, and its `set` function merges the update into the store one level deep.

Create a React-bound store with [`create`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand#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.

```mermaid
flowchart 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.

```tsx
import { 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](https://stackblitz.com/edit/vitejs-vite-5cu5ddvx) for more collection examples.

## When you use Immer

For nested updates, the [`immer`](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/zustand-middleware-immer#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](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/update-nested-state) for nested object update patterns.
- [Select state efficiently](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/select-state-efficiently) for selectors and avoiding unnecessary re-renders.
- [React quick start](https://bench-zustand-6l.atloria.app/p/bench-zustand-6l-TzFgfybqJX/developer/react-quick-start) for the separate issue of mutating an object from a previous render.
