Nested object changes
Updating deeply nested state
Zustand · Const
Zustand's immer middleware enables immutable updates using mutable-style draft syntax. It wraps a state creator and lets Immer produce the next state from draft mutations.
The immer middleware wraps a Zustand state creator so its update functions can modify an Immer draft directly. Immer then creates the immutable next state, avoiding manual copying while preserving Zustand's immutable-state model.
immer<T>(stateCreatorFn: StateCreator<T, [], []>): StateCreator<T, [['zustand/immer', never]], []>| Name | Type | Description |
|---|---|---|
stateCreatorFn | StateCreator<T, [], []> | The Zustand state creator to wrap; it receives set, get, and store and normally returns the store's state and actions. |
A state creator function typed with the ['zustand/immer', never] mutator.
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
type State = {
count: number
increment: (qty: number) => void
}
export const useCountStore = create<State>()(
immer((set) => ({
count: 0,
increment: (qty) =>
set((state) => {
state.count += qty
}),
})),
)The middleware lets the updater edit a draft directly while Immer produces the immutable next state.
Updating deeply nested state
Changing items inside arrays or keyed collections
Reducing repetitive spread and copy operations
immer accepts a Zustand state creator and returns another state creator. Its updater receives a draft, so code such as state.count += qty is allowed while Immer generates the immutable result.
Follow-up: What type-level mutator does it add? It changes the creator type to include ['zustand/immer', never].
The middleware is imported from zustand/middleware/immer, and Immer must be installed separately. It is applied inside create, wrapping the state creator.
Follow-up: Why is the dependency direct rather than transitive? The middleware relies on the Immer package at runtime, so the application must provide it.
Immer is intended to simplify updates to immutable data structures by removing the need to manually copy the entire object. The resulting state still follows Zustand's change-detection behavior.
Follow-up: What can happen with a non-immerable class? The class may be mutated outside a proxy, making current and next state equal and causing subscriptions not to run.
createzustand/middleware/immerImmerStateCreatorset