0
# Draft Management
1
2
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.
3
4
## Capabilities
5
6
### createDraft
7
8
Creates an Immer draft from the given base state, which can be modified until finalized with `finishDraft`.
9
10
```typescript { .api }
11
/**
12
* Create an Immer draft from the given base state
13
* @param base - Must be a plain object, array, or immerable object
14
* @returns A draft that can be modified until finalized
15
*/
16
function createDraft<T extends Objectish>(base: T): Draft<T>;
17
```
18
19
**Usage Examples:**
20
21
```typescript
22
import { createDraft, finishDraft } from "immer";
23
24
const baseState = {
25
user: { name: "Alice", age: 25 },
26
todos: ["Task 1", "Task 2"]
27
};
28
29
// Create a draft for manual manipulation
30
const draft = createDraft(baseState);
31
32
// Modify the draft
33
draft.user.age = 26;
34
draft.todos.push("Task 3");
35
draft.user.name = "Alice Smith";
36
37
// Finalize to get the immutable result
38
const nextState = finishDraft(draft);
39
40
console.log(baseState === nextState); // false
41
console.log(baseState.user.age); // 25 (unchanged)
42
console.log(nextState.user.age); // 26 (updated)
43
```
44
45
### finishDraft
46
47
Finalizes an Immer draft from a `createDraft` call, returning the base state (if no changes were made) or a modified copy.
48
49
```typescript { .api }
50
/**
51
* Finalize an Immer draft, returning base state or modified copy
52
* @param draft - Draft from createDraft call (must not be mutated afterwards)
53
* @param patchListener - Optional patch listener for tracking changes
54
* @returns Final immutable state
55
*/
56
function finishDraft<D extends Draft<any>>(
57
draft: D,
58
patchListener?: PatchListener
59
): D extends Draft<infer T> ? T : never;
60
```
61
62
**Usage Examples:**
63
64
```typescript
65
import { createDraft, finishDraft, enablePatches } from "immer";
66
67
// Enable patches for patch listener support
68
enablePatches();
69
70
const state = { items: [1, 2, 3], count: 3 };
71
const draft = createDraft(state);
72
73
// Modify draft
74
draft.items.push(4);
75
draft.count += 1;
76
77
// Finalize with patch tracking
78
const patches: Patch[] = [];
79
const inversePatches: Patch[] = [];
80
81
const result = finishDraft(draft, (p, ip) => {
82
patches.push(...p);
83
inversePatches.push(...ip);
84
});
85
86
console.log(patches);
87
// [
88
// { op: "add", path: ["items", 3], value: 4 },
89
// { op: "replace", path: ["count"], value: 4 }
90
// ]
91
92
// If no changes are made, returns the original object
93
const unchangedDraft = createDraft(state);
94
const unchanged = finishDraft(unchangedDraft);
95
console.log(unchanged === state); // true (no changes, same reference)
96
```
97
98
### current
99
100
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.
101
102
```typescript { .api }
103
/**
104
* Takes a snapshot of the current state of a draft
105
* @param value - Must be an Immer draft
106
* @returns Current state without proxy wrappers (for debugging/inspection)
107
*/
108
function current<T>(value: T): T;
109
```
110
111
**Usage Examples:**
112
113
```typescript
114
import { produce, current } from "immer";
115
116
const baseState = {
117
user: { name: "Bob", settings: { theme: "light" } },
118
data: [1, 2, 3]
119
};
120
121
const result = produce(baseState, draft => {
122
draft.user.name = "Robert";
123
draft.data.push(4);
124
125
// Get current snapshot for debugging or conditional logic
126
const currentState = current(draft);
127
console.log("Current state:", currentState);
128
// { user: { name: "Robert", settings: { theme: "light" } }, data: [1, 2, 3, 4] }
129
130
// Use current state for conditional updates
131
if (currentState.data.length > 3) {
132
draft.user.settings.theme = "dark";
133
}
134
135
// current() creates a deep copy, safe to mutate for comparisons
136
const snapshot = current(draft);
137
snapshot.temp = "this won't affect the draft";
138
});
139
140
// Useful for logging during complex transformations
141
const complexUpdate = produce(baseState, draft => {
142
// Step 1
143
draft.data.push(5, 6, 7);
144
console.log("After adding items:", current(draft).data);
145
146
// Step 2
147
draft.data = draft.data.filter(x => x % 2 === 0);
148
console.log("After filtering:", current(draft).data);
149
150
// Step 3
151
draft.user.name = draft.user.name.toUpperCase();
152
console.log("Final state:", current(draft));
153
});
154
```
155
156
### original
157
158
Gets the underlying object that is represented by the given draft. Returns `undefined` if the value is not a draft.
159
160
```typescript { .api }
161
/**
162
* Get the underlying object represented by the given draft
163
* @param value - An Immer draft
164
* @returns Original base object or undefined if not a draft
165
*/
166
function original<T>(value: T): T | undefined;
167
```
168
169
**Usage Examples:**
170
171
```typescript
172
import { produce, original } from "immer";
173
174
const baseState = {
175
items: ["apple", "banana"],
176
meta: { created: Date.now() }
177
};
178
179
const result = produce(baseState, draft => {
180
// Get reference to original state
181
const originalState = original(draft);
182
console.log(originalState === baseState); // true
183
184
// Access original values before mutations
185
const originalItems = original(draft.items);
186
console.log(originalItems); // ["apple", "banana"]
187
188
// Make mutations
189
draft.items.push("cherry");
190
draft.meta.updated = Date.now();
191
192
// Original is still unchanged
193
console.log(original(draft.items)); // ["apple", "banana"]
194
console.log(draft.items); // ["apple", "banana", "cherry"] (draft includes changes)
195
196
// Compare with original for conditional logic
197
if (draft.items.length > originalItems!.length) {
198
draft.meta.hasNewItems = true;
199
}
200
});
201
202
// original() returns undefined for non-drafts
203
const regularObject = { name: "test" };
204
console.log(original(regularObject)); // undefined
205
```
206
207
### isDraft
208
209
Returns true if the given value is an Immer draft.
210
211
```typescript { .api }
212
/**
213
* Returns true if the given value is an Immer draft
214
* @param value - Any value to check
215
* @returns Boolean indicating if value is a draft
216
*/
217
function isDraft(value: any): boolean;
218
```
219
220
**Usage Examples:**
221
222
```typescript
223
import { produce, createDraft, isDraft } from "immer";
224
225
const state = { name: "Alice", age: 30 };
226
227
console.log(isDraft(state)); // false
228
229
// Inside produce, parameters are drafts
230
const result = produce(state, draft => {
231
console.log(isDraft(draft)); // true
232
console.log(isDraft(draft.name)); // false (primitives are not drafts)
233
234
draft.nested = { value: 42 };
235
console.log(isDraft(draft.nested)); // true (objects within drafts are drafts)
236
});
237
238
// With createDraft
239
const draft = createDraft(state);
240
console.log(isDraft(draft)); // true
241
242
// Useful for conditional draft handling
243
function processValue(value: any) {
244
if (isDraft(value)) {
245
console.log("Processing draft - mutations will be tracked");
246
value.processed = true;
247
} else {
248
console.log("Processing regular object - mutations will affect original");
249
}
250
}
251
```
252
253
### isDraftable
254
255
Returns true if the given value can be drafted by Immer (i.e., can be converted into a draft).
256
257
```typescript { .api }
258
/**
259
* Returns true if the given value can be drafted by Immer
260
* @param value - Any value to check
261
* @returns Boolean indicating if value can be made into a draft
262
*/
263
function isDraftable(value: any): boolean;
264
```
265
266
**Usage Examples:**
267
268
```typescript
269
import { isDraftable } from "immer";
270
271
// Objects and arrays are draftable
272
console.log(isDraftable({})); // true
273
console.log(isDraftable([])); // true
274
console.log(isDraftable({ name: "Alice" })); // true
275
276
// Primitives are not draftable
277
console.log(isDraftable(42)); // false
278
console.log(isDraftable("string")); // false
279
console.log(isDraftable(true)); // false
280
console.log(isDraftable(null)); // false
281
console.log(isDraftable(undefined)); // false
282
283
// Built-in objects vary
284
console.log(isDraftable(new Date())); // false
285
console.log(isDraftable(new RegExp("test"))); // false
286
console.log(isDraftable(new Map())); // false (unless enableMapSet() is called)
287
console.log(isDraftable(new Set())); // false (unless enableMapSet() is called)
288
289
// Functions are not draftable
290
console.log(isDraftable(() => {})); // false
291
292
// Class instances are not draftable by default
293
class MyClass {
294
constructor(public value: number) {}
295
}
296
console.log(isDraftable(new MyClass(42))); // false
297
298
// But can be made draftable with immerable symbol
299
import { immerable } from "immer";
300
301
class DraftableClass {
302
[immerable] = true;
303
constructor(public value: number) {}
304
}
305
console.log(isDraftable(new DraftableClass(42))); // true
306
307
// Useful for validation before creating drafts
308
function safeDraft<T>(value: T): T {
309
if (!isDraftable(value)) {
310
throw new Error("Value cannot be drafted");
311
}
312
return createDraft(value);
313
}
314
```
315
316
## Advanced Draft Patterns
317
318
```typescript
319
import { createDraft, finishDraft, current, original, isDraft } from "immer";
320
321
// Manual draft lifecycle management
322
function manualUpdate<T>(state: T, updater: (draft: Draft<T>) => void): T {
323
const draft = createDraft(state);
324
try {
325
updater(draft);
326
return finishDraft(draft);
327
} catch (error) {
328
// Draft is automatically cleaned up on error
329
throw error;
330
}
331
}
332
333
// Incremental draft building
334
function buildComplexState() {
335
const draft = createDraft({ items: [], metadata: {} as any });
336
337
// Phase 1: Add items
338
draft.items.push("item1", "item2");
339
340
// Phase 2: Add metadata based on current state
341
const currentItems = current(draft).items;
342
draft.metadata.count = currentItems.length;
343
draft.metadata.created = Date.now();
344
345
// Phase 3: Conditional updates based on accumulated state
346
if (current(draft).items.length > 1) {
347
draft.metadata.type = "multi-item";
348
}
349
350
return finishDraft(draft);
351
}
352
353
// Draft composition
354
function composeDrafts<T>(base: T, ...updaters: Array<(draft: Draft<T>) => void>): T {
355
let result = base;
356
357
for (const updater of updaters) {
358
result = produce(result, updater);
359
}
360
361
return result;
362
}
363
```
364
365
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.