Reduce immutable-update boilerplate
Updating deeply nested objects
Zustand · Function
The immer middleware lets Zustand updates use mutable-looking draft syntax while producing immutable state. Wrap a state creator with it to simplify nested updates.
Wrap a Zustand state creator with immer to write nested updates using mutable-looking draft syntax. The middleware converts those draft mutations into immutable state updates and adds the Zustand Immer mutator type.
immer<T>(stateCreatorFn: StateCreator<T, [], []>): StateCreator<T, [['zustand/immer', never]], []>| Name | Type | Description |
|---|---|---|
stateCreatorFn | StateCreator<T, [], []> | A Zustand state creator that receives set, get, and store, and usually returns the state and actions to expose. |
A Zustand state creator function with the Immer mutator [['zustand/immer', never]].
immer.set, get, and store arguments.immerable marker; otherwise Immer may mutate the existing state without producing a distinct next state, so Zustand can skip subscriptions.immer as a direct dependency before importing this middleware.import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
type Store = {
person: { firstName: string; lastName: string }
setFirstName: (firstName: string) => void
}
export const usePersonStore = create<Store>()(
immer((set) => ({
person: { firstName: 'Barbara', lastName: 'Hepworth' },
setFirstName: (firstName) =>
set((state) => {
state.person.firstName = firstName
}),
})),
)The updater can mutate the draft directly; Immer produces the immutable next state.
Updating deeply nested objects
Managing structured records such as people or todos
Using Zustand with TypeScript
immer middleware do?immer accepts a Zustand state creator and returns another state creator. Its updater receives an Immer draft, so nested fields can be changed directly while Immer creates the immutable result.
Follow-up: How does the middleware appear in Zustand's type-level mutators? It adds ['zustand/immer', never] to the state creator's mutator tuple.
The documented signature is immer<T>(stateCreatorFn: StateCreator<T, [], []>): StateCreator<T, [['zustand/immer', never]], []>. The input creator receives set, get, and store, and normally returns the store's exposed state and actions.
Follow-up: What dependency is required? The immer library must be installed separately because the middleware uses it to produce immutable updates.
Wrap the state creator passed to create or createStore, then call set with a function that mutates the draft. This is especially useful for deeply nested objects because it avoids manually copying each level.
Follow-up: How should middleware ordering work with devtools? The TypeScript guide recommends devtools(immer(...)), putting devtools last so its setState mutation is preserved.
createcreateStoresetgetdevtoolsimmerableStateCreator