0
# Configuration & Utilities
1
2
Configuration options and utility functions for customizing Immer behavior, working with types, and managing immutable state operations.
3
4
## Capabilities
5
6
### setAutoFreeze
7
8
Controls whether Immer automatically freezes all copies created by produce functions. Freezing prevents accidental mutations of the immutable result.
9
10
```typescript { .api }
11
/**
12
* Pass true to automatically freeze all copies created by Immer
13
* @param value - Boolean to enable/disable auto-freezing
14
* @default true - Always freeze by default, even in production mode
15
*/
16
function setAutoFreeze(value: boolean): void;
17
```
18
19
**Usage Examples:**
20
21
```typescript
22
import { produce, setAutoFreeze } from "immer";
23
24
const baseState = { items: [1, 2, 3], meta: { count: 3 } };
25
26
// Default behavior - results are frozen
27
const result1 = produce(baseState, draft => {
28
draft.items.push(4);
29
});
30
31
console.log(Object.isFrozen(result1)); // true
32
console.log(Object.isFrozen(result1.items)); // true
33
34
// Attempting to mutate frozen objects throws in strict mode
35
try {
36
result1.items.push(5); // TypeError: Cannot add property 3, object is not extensible
37
} catch (error) {
38
console.log("Mutation prevented by freezing");
39
}
40
41
// Disable auto-freezing for performance or specific use cases
42
setAutoFreeze(false);
43
44
const result2 = produce(baseState, draft => {
45
draft.items.push(5);
46
});
47
48
console.log(Object.isFrozen(result2)); // false
49
console.log(Object.isFrozen(result2.items)); // false
50
51
// Now mutation is possible (but not recommended)
52
result2.items.push(6); // Works, but breaks immutability contract
53
54
// Re-enable freezing
55
setAutoFreeze(true);
56
57
const result3 = produce(result2, draft => {
58
draft.meta.updated = true;
59
});
60
61
console.log(Object.isFrozen(result3)); // true
62
```
63
64
### setUseStrictShallowCopy
65
66
Controls whether Immer uses strict shallow copy mode, which copies object descriptors such as getters, setters, and non-enumerable properties.
67
68
```typescript { .api }
69
/**
70
* Pass true to enable strict shallow copy
71
* @param value - Boolean or "class_only" to enable strict copying
72
* @default false - By default, Immer does not copy object descriptors
73
*/
74
function setUseStrictShallowCopy(value: boolean | "class_only"): void;
75
```
76
77
**Usage Examples:**
78
79
```typescript
80
import { produce, setUseStrictShallowCopy } from "immer";
81
82
// Object with descriptors
83
const baseState = {};
84
Object.defineProperty(baseState, 'computed', {
85
get() { return this._value * 2; },
86
set(val) { this._value = val; },
87
enumerable: false
88
});
89
Object.defineProperty(baseState, '_value', {
90
value: 10,
91
writable: true,
92
enumerable: false
93
});
94
95
// Default behavior - descriptors are not copied
96
const result1 = produce(baseState, draft => {
97
draft.newProp = 'added';
98
});
99
100
console.log(Object.getOwnPropertyDescriptor(result1, 'computed')); // undefined
101
console.log(result1._value); // undefined
102
103
// Enable strict shallow copy
104
setUseStrictShallowCopy(true);
105
106
const result2 = produce(baseState, draft => {
107
draft.anotherProp = 'also added';
108
});
109
110
console.log(Object.getOwnPropertyDescriptor(result2, 'computed')); // { get: [Function], set: [Function], enumerable: false, ... }
111
console.log(result2._value); // 10
112
console.log(result2.computed); // 20
113
114
// Class-only mode - only copy descriptors for class instances
115
setUseStrictShallowCopy("class_only");
116
117
class MyClass {
118
constructor(public value: number) {}
119
120
get doubled() {
121
return this.value * 2;
122
}
123
}
124
125
const classInstance = new MyClass(5);
126
const plainObject = { prop: 'value' };
127
128
const classResult = produce(classInstance, draft => {
129
draft.value = 10;
130
});
131
132
const plainResult = produce(plainObject, draft => {
133
draft.newProp = 'added';
134
});
135
136
// Descriptors copied for class instances but not plain objects in "class_only" mode
137
console.log(classResult.doubled); // 20 (getter preserved)
138
console.log(Object.getOwnPropertyDescriptor(plainResult, 'prop')); // Basic descriptor only
139
```
140
141
### freeze
142
143
Manually freezes draftable objects. This is primarily used internally by Immer but can be useful for manually freezing objects.
144
145
```typescript { .api }
146
/**
147
* Freezes draftable objects, returns the original object
148
* @param obj - Object to freeze
149
* @param deep - If true, freezes recursively (default: false)
150
* @returns The frozen object
151
*/
152
function freeze<T>(obj: T, deep?: boolean): T;
153
```
154
155
**Usage Examples:**
156
157
```typescript
158
import { freeze, isDraftable } from "immer";
159
160
const mutableObject = {
161
user: { name: "Alice", age: 30 },
162
items: [1, 2, 3],
163
config: { theme: "light" }
164
};
165
166
// Shallow freeze
167
const shallowFrozen = freeze(mutableObject);
168
169
console.log(Object.isFrozen(shallowFrozen)); // true
170
console.log(Object.isFrozen(shallowFrozen.user)); // false (shallow only)
171
172
// Attempting to modify top-level properties fails
173
try {
174
shallowFrozen.newProp = 'fails';
175
} catch (error) {
176
console.log("Top-level mutation prevented");
177
}
178
179
// But nested objects can still be modified
180
shallowFrozen.user.age = 31; // Works because user object is not frozen
181
console.log(shallowFrozen.user.age); // 31
182
183
// Deep freeze
184
const deepFrozen = freeze({
185
user: { name: "Bob", profile: { bio: "Developer" } },
186
data: [{ id: 1, value: "test" }]
187
}, true);
188
189
console.log(Object.isFrozen(deepFrozen)); // true
190
console.log(Object.isFrozen(deepFrozen.user)); // true
191
console.log(Object.isFrozen(deepFrozen.user.profile)); // true
192
console.log(Object.isFrozen(deepFrozen.data)); // true
193
console.log(Object.isFrozen(deepFrozen.data[0])); // true
194
195
// All levels are now immutable
196
try {
197
deepFrozen.user.name = "Charlie"; // TypeError in strict mode
198
} catch (error) {
199
console.log("Deep mutation prevented");
200
}
201
202
// freeze only affects draftable objects
203
const primitive = 42;
204
const frozenPrimitive = freeze(primitive);
205
console.log(frozenPrimitive === primitive); // true (primitives are unchanged)
206
207
// Works with arrays
208
const array = [{ id: 1 }, { id: 2 }];
209
const frozenArray = freeze(array, true);
210
console.log(Object.isFrozen(frozenArray)); // true
211
console.log(Object.isFrozen(frozenArray[0])); // true
212
```
213
214
### castDraft
215
216
Type casting utility that tells TypeScript to treat an immutable type as a draft type. This is a no-op at runtime but helps with type safety.
217
218
```typescript { .api }
219
/**
220
* This function is actually a no-op, but can be used to cast an immutable type
221
* to a draft type and make TypeScript happy
222
* @param value - Value to cast
223
* @returns Same value, typed as Draft<T>
224
*/
225
function castDraft<T>(value: T): Draft<T>;
226
```
227
228
**Usage Examples:**
229
230
```typescript
231
import { produce, castDraft, Draft, Immutable } from "immer";
232
233
// Scenario: Working with immutable data that needs to be treated as draft
234
function updateUserInPlace<T extends { user: { name: string; age: number } }>(
235
state: Immutable<T>,
236
updater: (user: Draft<T['user']>) => void
237
): T {
238
return produce(state, draft => {
239
// TypeScript knows draft.user is already a Draft<T['user']>
240
updater(draft.user);
241
});
242
}
243
244
// But sometimes you need to pass immutable data to a function expecting drafts
245
function processUser(user: Draft<{ name: string; age: number }>) {
246
user.name = user.name.toUpperCase();
247
user.age += 1;
248
}
249
250
const immutableState: Immutable<{ user: { name: string; age: number } }> = {
251
user: { name: "alice", age: 25 }
252
};
253
254
// This would cause TypeScript error without castDraft:
255
// processUser(immutableState.user); // Error: Immutable<> not assignable to Draft<>
256
257
const result = produce(immutableState, draft => {
258
// Cast immutable user to draft type for function that expects drafts
259
const userAsDraft = castDraft(immutableState.user);
260
261
// Now TypeScript accepts it, even though we're not actually using it
262
// (in practice, you'd use draft.user directly)
263
processUser(draft.user);
264
});
265
266
// More practical example: conditional draft processing
267
function conditionalUpdate<T>(
268
state: T,
269
condition: boolean,
270
updater: (draft: Draft<T>) => void
271
): T {
272
if (condition) {
273
return produce(state, updater);
274
} else {
275
// Return original state, but cast to maintain type compatibility
276
return castDraft(state) as T;
277
}
278
}
279
280
// Usage in generic contexts
281
interface Repository<T> {
282
update(updater: (draft: Draft<T>) => void): T;
283
getImmutable(): Immutable<T>;
284
}
285
286
class ImmutableRepository<T> implements Repository<T> {
287
constructor(private data: T) {}
288
289
update(updater: (draft: Draft<T>) => void): T {
290
this.data = produce(this.data, updater);
291
return this.data;
292
}
293
294
getImmutable(): Immutable<T> {
295
// Cast to immutable type for type safety
296
return this.data as Immutable<T>;
297
}
298
299
// Method that needs to work with both draft and immutable versions
300
processData(processor: (data: Draft<T>) => void): void {
301
if (this.isDraft(this.data)) {
302
processor(this.data as Draft<T>);
303
} else {
304
processor(castDraft(this.data));
305
}
306
}
307
308
private isDraft(value: any): boolean {
309
// In real implementation, would use isDraft from immer
310
return false; // Simplified for example
311
}
312
}
313
```
314
315
### castImmutable
316
317
Type casting utility that tells TypeScript to treat a mutable type as an immutable type. This is a no-op at runtime but helps with type safety.
318
319
```typescript { .api }
320
/**
321
* This function is actually a no-op, but can be used to cast a mutable type
322
* to an immutable type and make TypeScript happy
323
* @param value - Value to cast
324
* @returns Same value, typed as Immutable<T>
325
*/
326
function castImmutable<T>(value: T): Immutable<T>;
327
```
328
329
**Usage Examples:**
330
331
```typescript
332
import { produce, castImmutable, Draft, Immutable } from "immer";
333
334
// Scenario: Function that returns data that should be treated as immutable
335
function createImmutableUser(name: string, age: number): Immutable<{ name: string; age: number }> {
336
const user = { name, age }; // Mutable object
337
338
// Cast to immutable type to enforce immutability contract
339
return castImmutable(user);
340
}
341
342
// Usage with state management
343
class StateManager<T> {
344
private _state: T;
345
346
constructor(initialState: T) {
347
this._state = initialState;
348
}
349
350
// Return state as immutable to prevent external mutations
351
getState(): Immutable<T> {
352
return castImmutable(this._state);
353
}
354
355
// Update state and return new immutable version
356
updateState(updater: (draft: Draft<T>) => void): Immutable<T> {
357
this._state = produce(this._state, updater);
358
return castImmutable(this._state);
359
}
360
361
// Unsafe direct access for internal use only
362
private getMutableState(): T {
363
return this._state;
364
}
365
}
366
367
// Using with APIs that expect immutable data
368
interface ImmutableStore<T> {
369
data: Immutable<T>;
370
update(data: Immutable<T>): void;
371
}
372
373
function integrateWithStore<T>(
374
store: ImmutableStore<T>,
375
mutableData: T
376
): void {
377
// Cast mutable data to immutable for store compatibility
378
store.update(castImmutable(mutableData));
379
}
380
381
// Type-safe builder pattern
382
class ImmutableBuilder<T> {
383
private data: Partial<T> = {};
384
385
set<K extends keyof T>(key: K, value: T[K]): this {
386
this.data[key] = value;
387
return this;
388
}
389
390
build(): Immutable<T> {
391
if (!this.isComplete()) {
392
throw new Error("Incomplete data");
393
}
394
395
// Cast completed mutable data to immutable
396
return castImmutable(this.data as T);
397
}
398
399
private isComplete(): this is { data: T } {
400
// Simplified completeness check
401
return Object.keys(this.data).length > 0;
402
}
403
}
404
405
// Usage
406
const immutableUser = new ImmutableBuilder<{ name: string; age: number }>()
407
.set('name', 'Alice')
408
.set('age', 30)
409
.build();
410
411
// Now immutableUser has Immutable<> type, preventing accidental mutations
412
// immutableUser.name = "Bob"; // TypeScript error
413
```
414
415
## Custom Immer Instances
416
417
For advanced use cases, you can create custom Immer instances with specific configurations:
418
419
```typescript
420
import { Immer } from "immer";
421
422
// Create custom instance with specific settings
423
const customImmer = new Immer({
424
autoFreeze: false,
425
useStrictShallowCopy: "class_only"
426
});
427
428
// Use custom instance methods
429
const result = customImmer.produce(baseState, draft => {
430
draft.modified = true;
431
});
432
433
// Custom instance maintains its own configuration
434
console.log(Object.isFrozen(result)); // false (autoFreeze disabled)
435
436
// You can also create multiple instances with different configs
437
const debugImmer = new Immer({ autoFreeze: true });
438
const performanceImmer = new Immer({ autoFreeze: false });
439
440
// Use appropriate instance based on context
441
const debugResult = debugImmer.produce(data, draft => {
442
// Changes for debugging
443
});
444
445
const productionResult = performanceImmer.produce(data, draft => {
446
// Performance-critical changes
447
});
448
```
449
450
## Utility Type Guards
451
452
```typescript
453
import { isDraft, isDraftable, castDraft, castImmutable } from "immer";
454
455
// Type-safe utility functions
456
function safeCastDraft<T>(value: T): Draft<T> | null {
457
return isDraftable(value) ? castDraft(value) : null;
458
}
459
460
function safeCastImmutable<T>(value: T): Immutable<T> | null {
461
return isDraftable(value) ? castImmutable(value) : null;
462
}
463
464
// Generic processor that handles both drafts and regular objects
465
function processAnyValue<T>(
466
value: T,
467
processor: (v: Draft<T>) => void
468
): T {
469
if (isDraft(value)) {
470
processor(value as Draft<T>);
471
return value;
472
} else if (isDraftable(value)) {
473
return produce(value, processor);
474
} else {
475
// Cannot be drafted, return as-is
476
return value;
477
}
478
}
479
```
480
481
Configuration and utility functions provide fine-grained control over Immer's behavior and help maintain type safety in complex TypeScript applications.