Create immutable state by mutating the current one with structural sharing
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Advanced draft creation and manipulation utilities for fine-grained control over the immutable update process. These functions allow you to work directly with drafts outside of the standard produce workflow.
Creates an Immer draft from the given base state, which can be modified until finalized with finishDraft.
/**
* Create an Immer draft from the given base state
* @param base - Must be a plain object, array, or immerable object
* @returns A draft that can be modified until finalized
*/
function createDraft<T extends Objectish>(base: T): Draft<T>;Usage Examples:
import { createDraft, finishDraft } from "immer";
const baseState = {
user: { name: "Alice", age: 25 },
todos: ["Task 1", "Task 2"]
};
// Create a draft for manual manipulation
const draft = createDraft(baseState);
// Modify the draft
draft.user.age = 26;
draft.todos.push("Task 3");
draft.user.name = "Alice Smith";
// Finalize to get the immutable result
const nextState = finishDraft(draft);
console.log(baseState === nextState); // false
console.log(baseState.user.age); // 25 (unchanged)
console.log(nextState.user.age); // 26 (updated)Finalizes an Immer draft from a createDraft call, returning the base state (if no changes were made) or a modified copy.
/**
* Finalize an Immer draft, returning base state or modified copy
* @param draft - Draft from createDraft call (must not be mutated afterwards)
* @param patchListener - Optional patch listener for tracking changes
* @returns Final immutable state
*/
function finishDraft<D extends Draft<any>>(
draft: D,
patchListener?: PatchListener
): D extends Draft<infer T> ? T : never;Usage Examples:
import { createDraft, finishDraft, enablePatches } from "immer";
// Enable patches for patch listener support
enablePatches();
const state = { items: [1, 2, 3], count: 3 };
const draft = createDraft(state);
// Modify draft
draft.items.push(4);
draft.count += 1;
// Finalize with patch tracking
const patches: Patch[] = [];
const inversePatches: Patch[] = [];
const result = finishDraft(draft, (p, ip) => {
patches.push(...p);
inversePatches.push(...ip);
});
console.log(patches);
// [
// { op: "add", path: ["items", 3], value: 4 },
// { op: "replace", path: ["count"], value: 4 }
// ]
// If no changes are made, returns the original object
const unchangedDraft = createDraft(state);
const unchanged = finishDraft(unchangedDraft);
console.log(unchanged === state); // true (no changes, same reference)Takes a snapshot of the current state of a draft, useful for debugging or when you need to access the current state within a producer function.
/**
* Takes a snapshot of the current state of a draft
* @param value - Must be an Immer draft
* @returns Current state without proxy wrappers (for debugging/inspection)
*/
function current<T>(value: T): T;Usage Examples:
import { produce, current } from "immer";
const baseState = {
user: { name: "Bob", settings: { theme: "light" } },
data: [1, 2, 3]
};
const result = produce(baseState, draft => {
draft.user.name = "Robert";
draft.data.push(4);
// Get current snapshot for debugging or conditional logic
const currentState = current(draft);
console.log("Current state:", currentState);
// { user: { name: "Robert", settings: { theme: "light" } }, data: [1, 2, 3, 4] }
// Use current state for conditional updates
if (currentState.data.length > 3) {
draft.user.settings.theme = "dark";
}
// current() creates a deep copy, safe to mutate for comparisons
const snapshot = current(draft);
snapshot.temp = "this won't affect the draft";
});
// Useful for logging during complex transformations
const complexUpdate = produce(baseState, draft => {
// Step 1
draft.data.push(5, 6, 7);
console.log("After adding items:", current(draft).data);
// Step 2
draft.data = draft.data.filter(x => x % 2 === 0);
console.log("After filtering:", current(draft).data);
// Step 3
draft.user.name = draft.user.name.toUpperCase();
console.log("Final state:", current(draft));
});Gets the underlying object that is represented by the given draft. Returns undefined if the value is not a draft.
/**
* Get the underlying object represented by the given draft
* @param value - An Immer draft
* @returns Original base object or undefined if not a draft
*/
function original<T>(value: T): T | undefined;Usage Examples:
import { produce, original } from "immer";
const baseState = {
items: ["apple", "banana"],
meta: { created: Date.now() }
};
const result = produce(baseState, draft => {
// Get reference to original state
const originalState = original(draft);
console.log(originalState === baseState); // true
// Access original values before mutations
const originalItems = original(draft.items);
console.log(originalItems); // ["apple", "banana"]
// Make mutations
draft.items.push("cherry");
draft.meta.updated = Date.now();
// Original is still unchanged
console.log(original(draft.items)); // ["apple", "banana"]
console.log(draft.items); // ["apple", "banana", "cherry"] (draft includes changes)
// Compare with original for conditional logic
if (draft.items.length > originalItems!.length) {
draft.meta.hasNewItems = true;
}
});
// original() returns undefined for non-drafts
const regularObject = { name: "test" };
console.log(original(regularObject)); // undefinedReturns true if the given value is an Immer draft.
/**
* Returns true if the given value is an Immer draft
* @param value - Any value to check
* @returns Boolean indicating if value is a draft
*/
function isDraft(value: any): boolean;Usage Examples:
import { produce, createDraft, isDraft } from "immer";
const state = { name: "Alice", age: 30 };
console.log(isDraft(state)); // false
// Inside produce, parameters are drafts
const result = produce(state, draft => {
console.log(isDraft(draft)); // true
console.log(isDraft(draft.name)); // false (primitives are not drafts)
draft.nested = { value: 42 };
console.log(isDraft(draft.nested)); // true (objects within drafts are drafts)
});
// With createDraft
const draft = createDraft(state);
console.log(isDraft(draft)); // true
// Useful for conditional draft handling
function processValue(value: any) {
if (isDraft(value)) {
console.log("Processing draft - mutations will be tracked");
value.processed = true;
} else {
console.log("Processing regular object - mutations will affect original");
}
}Returns true if the given value can be drafted by Immer (i.e., can be converted into a draft).
/**
* Returns true if the given value can be drafted by Immer
* @param value - Any value to check
* @returns Boolean indicating if value can be made into a draft
*/
function isDraftable(value: any): boolean;Usage Examples:
import { isDraftable } from "immer";
// Objects and arrays are draftable
console.log(isDraftable({})); // true
console.log(isDraftable([])); // true
console.log(isDraftable({ name: "Alice" })); // true
// Primitives are not draftable
console.log(isDraftable(42)); // false
console.log(isDraftable("string")); // false
console.log(isDraftable(true)); // false
console.log(isDraftable(null)); // false
console.log(isDraftable(undefined)); // false
// Built-in objects vary
console.log(isDraftable(new Date())); // false
console.log(isDraftable(new RegExp("test"))); // false
console.log(isDraftable(new Map())); // false (unless enableMapSet() is called)
console.log(isDraftable(new Set())); // false (unless enableMapSet() is called)
// Functions are not draftable
console.log(isDraftable(() => {})); // false
// Class instances are not draftable by default
class MyClass {
constructor(public value: number) {}
}
console.log(isDraftable(new MyClass(42))); // false
// But can be made draftable with immerable symbol
import { immerable } from "immer";
class DraftableClass {
[immerable] = true;
constructor(public value: number) {}
}
console.log(isDraftable(new DraftableClass(42))); // true
// Useful for validation before creating drafts
function safeDraft<T>(value: T): T {
if (!isDraftable(value)) {
throw new Error("Value cannot be drafted");
}
return createDraft(value);
}import { createDraft, finishDraft, current, original, isDraft } from "immer";
// Manual draft lifecycle management
function manualUpdate<T>(state: T, updater: (draft: Draft<T>) => void): T {
const draft = createDraft(state);
try {
updater(draft);
return finishDraft(draft);
} catch (error) {
// Draft is automatically cleaned up on error
throw error;
}
}
// Incremental draft building
function buildComplexState() {
const draft = createDraft({ items: [], metadata: {} as any });
// Phase 1: Add items
draft.items.push("item1", "item2");
// Phase 2: Add metadata based on current state
const currentItems = current(draft).items;
draft.metadata.count = currentItems.length;
draft.metadata.created = Date.now();
// Phase 3: Conditional updates based on accumulated state
if (current(draft).items.length > 1) {
draft.metadata.type = "multi-item";
}
return finishDraft(draft);
}
// Draft composition
function composeDrafts<T>(base: T, ...updaters: Array<(draft: Draft<T>) => void>): T {
let result = base;
for (const updater of updaters) {
result = produce(result, updater);
}
return result;
}Draft management functions provide powerful tools for advanced Immer usage patterns where you need direct control over the draft lifecycle or want to build complex state transformations incrementally.