Mutative Middleware for Zustand
raw JSON →zustand-mutative is a middleware for Zustand that integrates the Mutative library, enabling efficient and convenient immutable state updates using a mutable syntax. It allows developers to modify Zustand state directly, similar to libraries like Immer, but claims significantly higher performance (2-6x faster than spread operations, over 10x faster than `zustand/middleware/immer`). The current stable version is 1.3.1. Releases appear to be driven by dependency updates and minor fixes, with new versions roughly every few months, often coinciding with updates to `mutative` or `zustand` itself. Its key differentiator is its performance advantage over other immutable update helpers within the Zustand ecosystem while providing a familiar mutable-style API for state management.
Common errors
error Error: 'mutative' does not contain an export named 'mutative'. ↓
mutative middleware from zustand-mutative like so: import { mutative } from 'zustand-mutative';. error TypeError: Cannot assign to read only property 'foo' of object '#<Object>' ↓
mutative middleware is correctly wrapped around your store definition. If you've enabled autoFreeze in Mutative options, all mutations must occur within the set callback provided by zustand-mutative. Check Mutative's strict mode documentation for more advanced debugging. error TS2307: Cannot find module 'zustand-mutative' or its corresponding type declarations. ↓
zustand-mutative is correctly installed. For NodeNext resolution issues, verify your tsconfig.json and package.json (exports field) are configured correctly, or update to zustand-mutative@^1.2.1 or later which includes fixes for these types of import issues. Warnings
breaking Version 1.1.0 upgraded its peer dependencies, specifically `zustand` to `v5` and `mutative` to `v1.1.0`. Users on older major versions of these dependencies will need to upgrade them to match, which may introduce breaking changes from those libraries themselves. ↓
gotcha When performing updates within the `set` function, always modify the `state` object directly. Returning a new object from `set` when using `mutative` middleware will bypass its capabilities and potentially lead to unexpected behavior or performance degradation. ↓
gotcha The `mutative` middleware is designed for scenarios where you need to perform complex nested updates efficiently. For very simple, shallow updates, directly using Zustand's `set` with spread syntax (`set((state) => ({ ...state, key: newValue }))`) might be sufficient and avoid the overhead of a middleware. ↓
breaking Version 1.2.0 added support for React v19 as a peer dependency. While this is generally an improvement, projects specifically locking to older React versions should be aware of potential dependency resolution conflicts if their package manager strictly enforces peer dependencies. ↓
Install
npm install zustand-mutative yarn add zustand-mutative pnpm add zustand-mutative Imports
- mutative wrong
const mutative = require('zustand-mutative').mutative;correctimport { mutative } from 'zustand-mutative'; - create wrong
import create from 'zustand';correctimport { create } from 'zustand'; - StoreDefinition wrong
create(mutative((set) => ({ ... })));correctcreate<State & Actions>()(mutative((set) => ({ ... })));
Quickstart
import { create } from 'zustand';
import { mutative } from 'zustand-mutative';
type State = {
count: number;
items: { id: string; value: number }[];
};
type Actions = {
increment: (qty: number) => void;
decrement: (qty: number) => void;
addItem: (id: string, value: number) => void;
updateItem: (id: string, newValue: number) => void;
};
export const useCountStore = create<State & Actions>()(
mutative((set) => ({
count: 0,
items: [],
increment: (qty: number) =>
set((state) => {
state.count += qty;
}),
decrement: (qty: number) =>
set((state) => {
state.count -= qty;
}),
addItem: (id: string, value: number) =>
set((state) => {
state.items.push({ id, value });
}),
updateItem: (id: string, newValue: number) =>
set((state) => {
const item = state.items.find((item) => item.id === id);
if (item) {
item.value = newValue;
}
}),
}))
);
// Example usage (not part of the store definition, but runnable)
// const store = useCountStore.getState();
// store.increment(5);
// store.addItem('a', 10);
// store.updateItem('a', 20);
// console.log(useCountStore.getState().count); // Expected: 5
// console.log(useCountStore.getState().items); // Expected: [{ id: 'a', value: 20 }]