Mutative Middleware for Zustand

raw JSON →
1.3.1 verified Thu Apr 23 auth: no javascript

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.

error Error: 'mutative' does not contain an export named 'mutative'.
cause Attempting to import `mutative` from the `mutative` package instead of `zustand-mutative`.
fix
Ensure you are importing the mutative middleware from zustand-mutative like so: import { mutative } from 'zustand-mutative';.
error TypeError: Cannot assign to read only property 'foo' of object '#<Object>'
cause This error can occur if `zustand-mutative` is not correctly applied or if `mutative`'s auto-freeze option is enabled in development, and you are trying to mutate a frozen object outside the `set` callback, or if strict mode issues are present.
fix
Ensure the 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.
cause TypeScript cannot find the module or its types. This often happens due to incorrect module resolution settings (e.g., `moduleResolution: 'NodeNext'` without proper `package.json` exports mapping) or outdated package installations.
fix
Ensure 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.
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.
fix Ensure your project's `zustand` and `mutative` versions meet the peer dependency requirements specified in `zustand-mutative`'s `package.json` (e.g., `zustand: ^4.0 || ^5.0`, `mutative: ^1.3.0`).
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.
fix Within the `set` callback provided to `mutative`, ensure all state changes are done by directly mutating the `state` parameter, e.g., `state.count += 1;`.
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.
fix Evaluate the complexity of your state updates. For simple, shallow changes, direct `set` with spread syntax is an option. For nested, complex, or performance-critical immutable updates, `zustand-mutative` is recommended.
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.
fix If encountering peer dependency issues with React, ensure your React version is within the supported range (`^17.0 || ^18.0 || ^19.0`). Consider using `legacy-peer-deps` or similar options with your package manager temporarily if you are unable to upgrade React immediately.
npm install zustand-mutative
yarn add zustand-mutative
pnpm add zustand-mutative

This quickstart demonstrates how to define a Zustand store using `zustand-mutative` middleware, enabling direct, mutable-style updates to an immutable state object, including primitive values and array/object mutations.

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 }]