Skip to main content

What does the immer middleware do?

immer is one of the most useful middlewares in Zustand, and it makes working with state simpler, safer, and more convenient, especially if you don't want to manually track immutability.


The essence in one sentence

The immer-middleware lets you change state "as if directly", but under the hood Zustand still creates an immutable copy (it does not mutate the original).


Without immer

In plain Zustand you have to explicitly return a new object so React understands that the state changed:

javascript
import { create } from 'zustand' const useTodoStore = create((set) => ({ todos: [], addTodo: (text) => set((state) => ({ todos: [...state.todos, { text, done: false }], })), }))

Here it's important not to mutate state.todos, otherwise React won't see the change. That is, you cannot do state.todos.push(...).


With immer

With immer you can write code in a mutable style, and the library creates an immutable copy under the hood:

javascript
import { create } from 'zustand' import { immer } from 'zustand/middleware/immer' const useTodoStore = create( immer((set) => ({ todos: [], addTodo: (text) => set((state) => { state.todos.push({ text, done: false }) // mutation is fine! }), toggleTodo: (index) => set((state) => { state.todos[index].done = !state.todos[index].done }), })) )

Now Zustand automatically:

  • creates a "draft" of the state (draft);
  • applies your changes to it;
  • creates a new version of the state with no mutations.

How this works under the hood

immer uses the Immer library, which implements the immutable state via Proxy concept.

Roughly like this:

  1. Zustand creates a proxy object (draft) of the state;
  2. You "mutate" it like a regular object;
  3. Immer records the changes and creates a new immutable copy of the state.

Advantages

AdvantageDescription
Clean codeYou write in a natural style without copying arrays and objects
SafetyGuarantees immutability (the original is not mutated)
SimplicityNo need to manually use spreads (...)
CompatibilityWorks with persist, devtools, and other middleware

Important to remember

  • Immer is a bit slower than "pure" immutable updates if updates are very frequent (for example, 60 times per second in an animation). But in typical applications the difference is unnoticeable.
  • Immer cannot be used together with direct mutations outside set() - it only works inside the set((state) => { ... }) function.

Summary

ParameterWithout immerWith immer
Updating an arrayset({ todos: [...state.todos, newTodo] })set((s) => { s.todos.push(newTodo) })
Immutabilitymanualautomatic
Codeverboseconcise
Safetydepends on the developerguaranteed by Immer

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.