Mutative
Mutative is a JavaScript library designed for performing efficient immutable updates on data structures, drawing inspiration from libraries like Immer but with a strong focus on performance. It allows developers to write 'mutative' logic inside a producer function, and Mutative handles the underlying copy-on-write mechanism to return a new, immutably updated state. The library is currently at version 1.3.0 and exhibits an active release cadence, with frequent patch and minor updates. Key differentiators include its reported performance benefits, claiming to be 2-6x faster than naive handcrafted reducers using spread operators and over 10x faster than Immer, primarily through shallow copy optimization, lazy drafts, and an optimized finalization process. It aims to address issues like Immer's mandatory auto-freeze and improve type inference and handling of various immutability edge cases.
Common errors
-
TypeError: Cannot perform 'set' on a proxy that has been revoked
cause Attempting to modify a Mutative draft object or its properties after the producer function has completed and the proxy has been revoked (finalized).fixEnsure all modifications to the draft state are confined within the producer function passed to `create`. Do not store or attempt to modify the `draft` object reference after `create` returns. -
TypeError: Cannot assign to read only property 'propertyName' of object '[object Object]'
cause Trying to directly mutate the object returned by `create`. This object is frozen and immutable, meaning its properties cannot be changed directly.fixAlways use the `create` function with a producer callback to make changes. The return value of `create` is the new, immutable state. -
TypeError: create is not a function
cause Incorrect import statement for the `create` function, particularly common with CommonJS `require` when `mutative` is primarily an ESM package, or if using a default import instead of a named import.fixFor ES Modules, use `import { create } from 'mutative'`. For CommonJS in environments where it's supported, use `const { create } = require('mutative')`.
Warnings
- gotcha Mutative's significant performance claims (e.g., '10x faster than Immer', '6x faster than naive handcrafted reducer') are highly dependent on the specific benchmark scenarios and comparison implementations. Real-world performance may vary, and for simple updates, the difference might be negligible or even in favor of highly optimized manual spread operations.
- gotcha When working with `mutative`, ensure all modifications occur strictly within the producer function provided to `create`. Attempting to modify the draft object or any part of the returned immutable state outside this function will lead to errors or unexpected behavior due to proxy revocation or frozen objects.
- deprecated The `current()` API underwent multiple refactorings and a temporary revert in early 1.x versions (specifically around v1.0.7-v1.0.8). While now stable in later 1.x releases, developers on older patch versions might encounter inconsistencies or incorrect behavior when using `current()` to access the latest draft state.
- gotcha Mutative uses JavaScript Proxies for its copy-on-write mechanism. While powerful, Proxies can sometimes have subtle interactions or edge cases with certain non-plain JavaScript objects (e.g., custom classes, complex data structures with internal state not exposed via standard properties) or when strict equality checks are used in specific contexts.
Install
-
npm install mutative -
yarn add mutative -
pnpm add mutative
Imports
- create
const create = require('mutative')import { create } from 'mutative' - apply
const apply = require('mutative')import { apply } from 'mutative' - original
import original from 'mutative'
import { original } from 'mutative' - IPatch
import { IPatch } from 'mutative'import type { IPatch } from 'mutative'
Quickstart
import { create, original } from 'mutative';
interface TodoItem {
id: number;
text: string;
completed: boolean;
}
interface AppState {
todos: TodoItem[];
filter: 'all' | 'active' | 'completed';
user: { name: string; id: string };
}
const initialState: AppState = {
todos: [
{ id: 1, text: 'Learn Mutative', completed: false },
{ id: 2, text: 'Build Something', completed: true },
],
filter: 'all',
user: { name: 'John Doe', id: 'user-123' },
};
// Update an item and add a new one
const newState = create(initialState, (draft) => {
const todoToUpdate = draft.todos.find(todo => todo.id === 1);
if (todoToUpdate) {
todoToUpdate.completed = true;
todoToUpdate.text = 'Learn Mutative (done!)';
}
draft.todos.push({ id: 3, text: 'Deploy App', completed: false });
draft.user.name = 'Jane Doe';
});
console.log('Initial State:', original(initialState));
console.log('New State:', newState);
console.log('Are states different?', initialState !== newState); // true
console.log('Is todos array different?', initialState.todos !== newState.todos); // true
console.log('Are other parts (e.g., filter) unchanged by reference?', initialState.filter === newState.filter); // true
console.log('Original todo 1 text (from initial state):', initialState.todos[0]?.text); // Learn Mutative
console.log('New todo 1 text (from new state):', newState.todos[0]?.text); // Learn Mutative (done!)