Skip to main content
Like React’s useState, Zustand requires state updates to be immutable. Understanding how set merges state is crucial for working with Zustand effectively.

Basic State Updates

Here’s a typical example of updating state:
import { create } from 'zustand'

const useCountStore = create((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

Automatic Shallow Merging

Because state is immutable, updates should look like this:
set((state) => ({ ...state, count: state.count + 1 }))
However, set automatically performs shallow merging, so you can skip the spread operator:
set((state) => ({ count: state.count + 1 }))
The set function automatically merges the partial state object with the current state at the top level. You only need to return the properties you want to update.

Nested Objects

The set function only merges state at one level deep. For nested objects, you must merge them explicitly using the spread operator.
When working with nested objects, you need to manually merge nested properties:
import { create } from 'zustand'

const useCountStore = create((set) => ({
  nested: { count: 0 },
  inc: () =>
    set((state) => ({
      nested: { ...state.nested, count: state.nested.count + 1 },
    })),
}))

Example with Multiple Nesting Levels

import { create } from 'zustand'

const useStore = create((set) => ({
  user: {
    profile: {
      name: 'John',
      age: 30,
    },
    settings: {
      theme: 'dark',
    },
  },
  updateName: (name) =>
    set((state) => ({
      user: {
        ...state.user,
        profile: {
          ...state.user.profile,
          name,
        },
      },
    })),
}))
For complex nested state updates, consider using libraries that help with immutable updates. See the Updating State guide for more information.

The Replace Flag

To disable the automatic merging behavior, specify true as the second parameter to set:
set((state) => newState, true)
Using the replace flag will completely replace the entire store state. All properties not included in newState will be deleted. Use with caution!

When to Use Replace

const useStore = create((set) => ({
  count: 0,
  name: 'John',
  increment: () => set((state) => ({ count: state.count + 1 })),
  // Result: { count: 1, name: 'John', increment: [Function] }
  // The 'name' property is preserved
}))

Comparison with React’s setState

const [state, setState] = useState({ count: 0, name: 'John' })

// Requires manual spreading
setState({ ...state, count: state.count + 1 })

// Or use functional update with spreading
setState((prev) => ({ ...prev, count: prev.count + 1 }))

Key Takeaways

Shallow Merge

set automatically merges at the top level—you don’t need the spread operator for root properties

Nested Objects

Nested objects require manual spreading—automatic merging only works one level deep

Replace Flag

Use set(newState, true) to completely replace the store instead of merging

Immutability

Always return new objects—never mutate existing state directly

Next Steps

For complex use cases with deeply nested objects, check out:

Build docs developers (and LLMs) love