Create immutable state by mutating the current one with structural sharing
npx @tessl/cli install tessl/npm-immer@10.0.0Immer is a comprehensive immutable state management library that allows developers to create the next immutable state by simply mutating the current state tree. It provides a powerful produce function that works with structural sharing to efficiently create new immutable data structures while maintaining the familiar mutative JavaScript syntax.
npm install immerimport { produce, Draft, Immutable, enablePatches } from "immer";For CommonJS:
const { produce, enablePatches } = require("immer");import { produce } from "immer";
// Basic state mutation
const baseState = {
todos: [
{ id: 1, text: "Learn Immer", done: false },
{ id: 2, text: "Use Immer in project", done: false }
],
user: { name: "John", age: 30 }
};
// Create new state by "mutating" a draft
const nextState = produce(baseState, draft => {
draft.todos[0].done = true;
draft.user.age = 31;
draft.todos.push({ id: 3, text: "Master Immer", done: false });
});
// baseState is unchanged, nextState contains the updates
console.log(baseState === nextState); // false
console.log(baseState.todos === nextState.todos); // false (structural sharing)Immer is built around several key components:
Draft<T> and Immutable<T> utility typesThe main produce function and related production utilities for creating immutable state through mutation-like syntax.
function produce<T>(
base: T,
recipe: (draft: Draft<T>) => void | T | undefined
): T;
function produce<T>(
recipe: (draft: Draft<T>) => void | T | undefined
): (base: T) => T;
function produceWithPatches<T>(
base: T,
recipe: (draft: Draft<T>) => void | T | undefined
): [T, Patch[], Patch[]];Direct draft creation and manipulation utilities for advanced use cases and fine-grained control over the immutable update process.
function createDraft<T extends Objectish>(base: T): Draft<T>;
function finishDraft<D extends Draft<any>>(
draft: D,
patchListener?: PatchListener
): D extends Draft<infer T> ? T : never;
function current<T>(value: T): T;
function original<T>(value: T): T | undefined;Advanced patch tracking system for implementing undo/redo, debugging, and state synchronization features.
function applyPatches<T>(base: T, patches: readonly Patch[]): T;
function enablePatches(): void;
interface Patch {
op: "replace" | "remove" | "add";
path: (string | number)[];
value?: any;
}Configuration options and utility functions for customizing Immer behavior and working with types.
function setAutoFreeze(value: boolean): void;
function setUseStrictShallowCopy(value: boolean | "class_only"): void;
function freeze<T>(obj: T, deep?: boolean): T;
function isDraft(value: any): boolean;
function isDraftable(value: any): boolean;
function castDraft<T>(value: T): Draft<T>;
function castImmutable<T>(value: T): Immutable<T>;Plugin that enables Immer to work with Map and Set objects, providing draft versions that track mutations.
function enableMapSet(): void;Enables drafting of Map and Set instances with full mutation tracking and structural sharing support.
Usage Examples:
import { produce, enableMapSet } from "immer";
// Enable Map/Set support
enableMapSet();
// Working with Maps
const stateWithMap = {
userMap: new Map([
["user1", { name: "Alice", age: 30 }],
["user2", { name: "Bob", age: 25 }]
])
};
const updatedState = produce(stateWithMap, draft => {
// Map methods work normally in drafts
draft.userMap.set("user3", { name: "Charlie", age: 35 });
draft.userMap.get("user1")!.age = 31;
draft.userMap.delete("user2");
});
// Working with Sets
const stateWithSet = {
tags: new Set(["javascript", "react", "typescript"])
};
const updatedSet = produce(stateWithSet, draft => {
draft.tags.add("immer");
draft.tags.delete("react");
});type Draft<T> = T extends PrimitiveType
? T
: T extends AtomicObject
? T
: T extends ReadonlyMap<infer K, infer V>
? Map<Draft<K>, Draft<V>>
: T extends ReadonlySet<infer V>
? Set<Draft<V>>
: T extends WeakReferences
? T
: T extends object
? WritableDraft<T>
: T;
type PrimitiveType = number | string | boolean;
type AtomicObject = Function | Promise<any> | Date | RegExp;
type Immutable<T> = T extends PrimitiveType
? T
: T extends AtomicObject
? T
: T extends ReadonlyMap<infer K, infer V>
? ReadonlyMap<Immutable<K>, Immutable<V>>
: T extends ReadonlySet<infer V>
? ReadonlySet<Immutable<V>>
: T extends WeakReferences
? T
: T extends object
? { readonly [K in keyof T]: Immutable<T[K]> }
: T;
type WritableDraft<T> = {
-readonly [K in keyof T]: Draft<T[K]>;
};
type Producer<T> = (draft: Draft<T>) => T | void | undefined | (T extends undefined ? typeof NOTHING : never);
type PatchListener = (patches: Patch[], inversePatches: Patch[]) => void;
type Objectish = object;const nothing: unique symbol;
const immerable: unique symbol;The nothing symbol is used in producer functions to indicate that a property should be deleted or set to undefined. When returned from a producer, it causes the entire state to become undefined.
The immerable symbol can be added to class prototypes to make them draftable by Immer. Classes marked with this symbol will be treated as plain objects during drafting.
class Immer {
constructor(config?: {
autoFreeze?: boolean;
useStrictShallowCopy?: boolean | "class_only";
});
produce: IProduce;
produceWithPatches: IProduceWithPatches;
createDraft<T extends Objectish>(base: T): Draft<T>;
finishDraft<D extends Draft<any>>(
draft: D,
patchListener?: PatchListener
): D extends Draft<infer T> ? T : never;
setAutoFreeze(value: boolean): void;
setUseStrictShallowCopy(value: boolean | "class_only"): void;
applyPatches<T extends Objectish>(base: T, patches: readonly Patch[]): T;
}
type StrictMode = boolean | "class_only";
type Objectish = object;
type ValidRecipeReturnType<State> =
| State
| void
| undefined
| (State extends undefined ? typeof NOTHING : never);
type WeakReferences = WeakMap<any, any> | WeakSet<any>;Create custom Immer instances with specific configurations for different use cases.
import { produce, enablePatches, applyPatches, Immer } from "immer";
// Enable patches plugin for tracking changes
enablePatches();
// Use produceWithPatches to get change information
const [nextState, patches, inversePatches] = produceWithPatches(baseState, draft => {
draft.todos[0].done = true;
});
// Apply patches to recreate the same state change
const recreatedState = applyPatches(baseState, patches);
// Create isolated Immer instance with custom configuration
const immer = new Immer({
autoFreeze: false,
useStrictShallowCopy: "class_only"
});
const result = immer.produce(baseState, draft => {
draft.user.name = "Jane";
});