A Mutative middleware for Zustand enhances the efficiency of immutable state updates.
With the Mutative middleware, you can simplify the handling of immutable data in Zustand in a mutable way, allowing you to use immutable state more conveniently.
zustand-mutative runs the recipes that set() receives with Mutative 2, where zustand/middleware/immer runs them with Immer. In Mutative's benchmark of 97 workloads, Mutative 2.0 was about 3.6x as fast as Immer 11.1.18 with matched settings, and 6.6x as fast with each library's defaults, under which Immer freezes the next state and Mutative does not (geometric means). A hand-written reducer that copies only what it changes was faster still in most of those workloads. The performance summary has the complete results, the method and their limits.
In order to use the Mutative middleware in Zustand, you will need to install Mutative and Zustand as a direct dependency.
npm install zustand-mutative zustand mutative
# Or use any package manager of your choice.| zustand-mutative | mutative |
|---|---|
| 2.x | 2.x |
| 1.x | 1.x |
zustand-mutative 2.x declares mutative ^2.0.0 as its peer dependency, so upgrade both together:
npm install zustand-mutative@2 mutative@2The middleware API is unchanged: set() takes the same recipes, and mutative() takes the same options. The Mutative changes that can affect a store are:
- With
enableAutoFreeze: true, Map and Set instances in the state are frozen too, so adding a property to them or replacing one of their methods fails. - Mutative selects its development or production build from
process.env.NODE_ENV. Define it in every bundle, including bundles for Node.js, and in production, match errors by theirMinified Mutative error #<code>codes instead of their messages. - Mutative targets ES2018, and so does
zustand-mutative; transpile both for older engines. Without a bundler, mapmutativeto its production ESM build,mutative/dist/mutative.esm.production.min.mjs, as its default ESM build readsprocess, or load its UMD build, which defines theMutativeglobal that the UMD build ofzustand-mutativereads.
See the migration guide for every change in Mutative v2.
import { create } from 'zustand';
import { mutative } from 'zustand-mutative';
type State = {
count: number;
};
type Actions = {
increment: (qty: number) => void;
decrement: (qty: number) => void;
};
export const useCountStore = create<State & Actions>()(
mutative((set) => ({
count: 0,
increment: (qty: number) =>
set((state) => {
state.count += qty;
}),
decrement: (qty: number) =>
set((state) => {
state.count -= qty;
}),
}))
);With enableAutoFreeze: true, Mutative deeply freezes the next state that a recipe passed to set() produces. The initial state and objects passed to set() directly stay unfrozen until the next set() with a recipe, and the root state object stays unfrozen unless set() replaces the state, as in set(recipe, true): by default, Zustand merges the next state into a new root object.
zustand-mutative is inspired by zustand/middleware/immer.
zustand-mutative is MIT licensed.