CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-immer

Create immutable state by mutating the current one with structural sharing

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

draft-management.mddocs/

Draft Management

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.

Capabilities

createDraft

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)

finishDraft

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)

current

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));
});

original

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)); // undefined

isDraft

Returns 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");
  }
}

isDraftable

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);
}

Advanced Draft Patterns

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.

docs

configuration-utilities.md

core-production.md

draft-management.md

index.md

patches-system.md

tile.json