0
# Advanced Utilities
1
2
Extended functionality including reactive effects, key-specific subscriptions, DevTools integration, deep cloning, and specialized collections for Maps and Sets.
3
4
## Capabilities
5
6
### Reactive Effects (watch)
7
8
Creates a reactive effect that automatically tracks proxy objects and re-evaluates whenever tracked objects update. Provides Vue.js-style computed effects for Valtio.
9
10
```typescript { .api }
11
/**
12
* Creates a reactive effect that automatically tracks proxy objects
13
* Callback is invoked immediately to detect tracked objects
14
* @param callback - Function that receives a get function for tracking proxy objects
15
* @param options - Configuration options
16
* @param options.sync - If true, notifications happen synchronously
17
* @returns Cleanup function to stop the reactive effect
18
*/
19
function watch(
20
callback: (get: <T extends object>(proxyObject: T) => T) => void | (() => void) | Promise<void | (() => void)>,
21
options?: { sync?: boolean }
22
): () => void;
23
```
24
25
**Usage Examples:**
26
27
```typescript
28
import { proxy, watch } from "valtio/utils";
29
30
const state = proxy({ count: 0, multiplier: 2 });
31
32
// Basic reactive effect
33
const cleanup = watch((get) => {
34
const { count, multiplier } = get(state);
35
console.log("Computed value:", count * multiplier);
36
});
37
38
state.count = 5; // Logs: "Computed value: 10"
39
state.multiplier = 3; // Logs: "Computed value: 15"
40
41
// Effect with cleanup
42
const cleanupWithSideEffect = watch((get) => {
43
const { count } = get(state);
44
45
// Set up side effect
46
const timer = setInterval(() => {
47
console.log("Current count:", count);
48
}, 1000);
49
50
// Return cleanup function
51
return () => clearInterval(timer);
52
});
53
54
// Nested watch calls are automatically cleaned up
55
watch((get) => {
56
const { user } = get(appState);
57
58
if (user) {
59
// This watch will be cleaned up when user changes
60
const innerCleanup = watch((get) => {
61
const { preferences } = get(user);
62
console.log("User preferences:", preferences);
63
});
64
}
65
});
66
67
// Stop watching
68
cleanup();
69
```
70
71
### Key-Specific Subscriptions
72
73
Subscribes to changes of a specific property key, providing more efficient notifications than general subscriptions.
74
75
```typescript { .api }
76
/**
77
* Subscribes to a specific property key of a proxy object
78
* Only fires when the specified property changes
79
* @param proxyObject - The proxy object to subscribe to
80
* @param key - The property key to watch
81
* @param callback - Function called when the key changes
82
* @param notifyInSync - If true, notifications happen synchronously
83
* @returns Unsubscribe function
84
*/
85
function subscribeKey<T extends object, K extends keyof T>(
86
proxyObject: T,
87
key: K,
88
callback: (value: T[K]) => void,
89
notifyInSync?: boolean
90
): () => void;
91
```
92
93
**Usage Examples:**
94
95
```typescript
96
import { proxy, subscribeKey } from "valtio/utils";
97
98
const state = proxy({ count: 0, name: "Alice", theme: "light" });
99
100
// Subscribe to specific key
101
const unsubscribe = subscribeKey(state, "count", (newCount) => {
102
console.log("Count changed to:", newCount);
103
});
104
105
state.count++; // Logs: "Count changed to: 1"
106
state.name = "Bob"; // No log (different key)
107
state.count = 5; // Logs: "Count changed to: 5"
108
109
// Multiple subscriptions to same key
110
const unsub1 = subscribeKey(state, "theme", (theme) => {
111
document.body.className = theme;
112
});
113
114
const unsub2 = subscribeKey(state, "theme", (theme) => {
115
localStorage.setItem("theme", theme);
116
});
117
118
state.theme = "dark"; // Both callbacks fire
119
120
// Cleanup
121
unsubscribe();
122
unsub1();
123
unsub2();
124
```
125
126
### Redux DevTools Integration
127
128
Connects a proxy object to Redux DevTools Extension for state debugging and time-travel debugging.
129
130
```typescript { .api }
131
/**
132
* Connects a proxy object to Redux DevTools Extension
133
* Enables real-time monitoring and time-travel debugging
134
* Limitation: Only plain objects/values are supported
135
* @param proxyObject - The proxy object to connect to DevTools
136
* @param options - Configuration options for the DevTools connection
137
* @param options.enabled - Explicitly enable or disable the connection
138
* @param options.name - Name to display in DevTools
139
* @returns Unsubscribe function or undefined if connection failed
140
*/
141
function devtools<T extends object>(
142
proxyObject: T,
143
options?: {
144
enabled?: boolean;
145
name?: string;
146
[key: string]: any; // Additional Redux DevTools options
147
}
148
): (() => void) | undefined;
149
```
150
151
**Usage Examples:**
152
153
```typescript
154
import { proxy, devtools } from "valtio/utils";
155
156
const state = proxy({ count: 0, user: { name: "Alice" } });
157
158
// Basic DevTools connection
159
const disconnect = devtools(state, {
160
name: "App State",
161
enabled: true
162
});
163
164
// DevTools will show:
165
// - Current state snapshots
166
// - Action history with operation details
167
// - Time-travel debugging capabilities
168
169
state.count++; // Shows as "set:count" in DevTools
170
state.user.name = "Bob"; // Shows as "set:user.name" in DevTools
171
172
// Multiple stores
173
const userState = proxy({ profile: {}, settings: {} });
174
const appState = proxy({ theme: "light", language: "en" });
175
176
devtools(userState, { name: "User Store" });
177
devtools(appState, { name: "App Store" });
178
179
// Conditional enabling
180
devtools(state, {
181
enabled: process.env.NODE_ENV === "development",
182
name: "Debug State"
183
});
184
185
// Cleanup
186
disconnect?.();
187
```
188
189
### Deep Cloning
190
191
Creates a deep clone of an object while maintaining proxy behavior for Maps and Sets.
192
193
```typescript { .api }
194
/**
195
* Creates a deep clone of an object, maintaining proxy behavior for Maps and Sets
196
* @param obj - The object to clone
197
* @param getRefSet - Function to get the set of reference objects (optional)
198
* @returns A deep clone of the input object
199
*/
200
function deepClone<T>(
201
obj: T,
202
getRefSet?: () => WeakSet<object>
203
): T;
204
```
205
206
**Usage Examples:**
207
208
```typescript
209
import { proxy, deepClone, ref } from "valtio";
210
import { deepClone } from "valtio/utils";
211
212
const originalState = proxy({
213
user: { name: "Alice", preferences: { theme: "dark" } },
214
items: [1, 2, { nested: true }],
215
metadata: ref({ immutable: "data" })
216
});
217
218
// Deep clone the state
219
const clonedState = deepClone(originalState);
220
221
// Cloned state is independent
222
clonedState.user.name = "Bob";
223
console.log(originalState.user.name); // Still "Alice"
224
225
// Referenced objects are preserved
226
console.log(clonedState.metadata === originalState.metadata); // true
227
228
// Works with complex structures
229
const complexState = proxy({
230
map: new Map([["key", "value"]]),
231
set: new Set([1, 2, 3]),
232
date: new Date(),
233
nested: { deep: { structure: "value" } }
234
});
235
236
const cloned = deepClone(complexState);
237
```
238
239
### Proxy Sets
240
241
Creates a reactive Set that integrates with Valtio's proxy system, extending the standard Set API with additional set operations.
242
243
```typescript { .api }
244
/**
245
* Creates a reactive Set that integrates with Valtio's proxy system
246
* Includes extended set operations like union, intersection, difference
247
* @param initialValues - Initial values to populate the Set
248
* @returns A reactive proxy Set with extended methods
249
* @throws TypeError if initialValues is not iterable
250
*/
251
function proxySet<T>(initialValues?: Iterable<T> | null): ProxySet<T>;
252
253
/**
254
* Determines if an object is a proxy Set created with proxySet
255
* @param obj - The object to check
256
* @returns True if the object is a proxy Set
257
*/
258
function isProxySet(obj: object): boolean;
259
260
```
261
262
**Usage Examples:**
263
264
```typescript
265
import { proxy, useSnapshot } from "valtio";
266
import { proxySet, isProxySet } from "valtio/utils";
267
268
// Basic usage
269
const tags = proxySet(["javascript", "react", "typescript"]);
270
271
// Use within proxy state
272
const state = proxy({
273
selectedTags: proxySet<string>(),
274
allTags: proxySet(["javascript", "react", "vue", "typescript"])
275
});
276
277
// React component
278
function TagSelector() {
279
const { selectedTags, allTags } = useSnapshot(state);
280
281
return (
282
<div>
283
{[...allTags].map(tag => (
284
<button
285
key={tag}
286
onClick={() => {
287
if (state.selectedTags.has(tag)) {
288
state.selectedTags.delete(tag);
289
} else {
290
state.selectedTags.add(tag);
291
}
292
}}
293
className={selectedTags.has(tag) ? "selected" : ""}
294
>
295
{tag}
296
</button>
297
))}
298
</div>
299
);
300
}
301
302
// Extended set operations
303
const set1 = proxySet([1, 2, 3, 4]);
304
const set2 = proxySet([3, 4, 5, 6]);
305
306
const union = set1.union(set2); // {1, 2, 3, 4, 5, 6}
307
const intersection = set1.intersection(set2); // {3, 4}
308
const difference = set1.difference(set2); // {1, 2}
309
const symmetric = set1.symmetricDifference(set2); // {1, 2, 5, 6}
310
311
console.log(set1.isSubsetOf(set2)); // false
312
console.log(set1.isSupersetOf(new Set([1, 2]))); // true
313
console.log(set1.isDisjointFrom(new Set([7, 8]))); // true
314
315
// Type checking
316
console.log(isProxySet(set1)); // true
317
console.log(isProxySet(new Set())); // false
318
```
319
320
### Proxy Maps
321
322
Creates a reactive Map that integrates with Valtio's proxy system with the same API as standard JavaScript Map.
323
324
```typescript { .api }
325
/**
326
* Creates a reactive Map that integrates with Valtio's proxy system
327
* The API is the same as the standard JavaScript Map
328
* @param entries - Initial key-value pairs to populate the Map
329
* @returns A proxy Map object that tracks changes
330
* @throws TypeError if entries is not iterable
331
*/
332
function proxyMap<K, V>(entries?: Iterable<[K, V]> | null): ProxyMap<K, V>;
333
334
/**
335
* Determines if an object is a proxy Map created with proxyMap
336
* @param obj - The object to check
337
* @returns True if the object is a proxy Map
338
*/
339
function isProxyMap(obj: object): boolean;
340
```
341
342
**Usage Examples:**
343
344
```typescript
345
import { proxy, useSnapshot, ref } from "valtio";
346
import { proxyMap, isProxyMap } from "valtio/utils";
347
348
// Basic usage
349
const cache = proxyMap<string, any>();
350
351
// Use within proxy state
352
const state = proxy({
353
userCache: proxyMap<number, User>(),
354
settings: proxyMap([
355
["theme", "dark"],
356
["language", "en"]
357
])
358
});
359
360
// React component
361
function UserList() {
362
const { userCache } = useSnapshot(state);
363
364
return (
365
<div>
366
{[...userCache.entries()].map(([id, user]) => (
367
<div key={id}>
368
{user.name}
369
<button onClick={() => state.userCache.delete(id)}>
370
Remove
371
</button>
372
</div>
373
))}
374
</div>
375
);
376
}
377
378
// Standard Map operations
379
state.userCache.set(1, { name: "Alice", age: 25 });
380
state.userCache.set(2, { name: "Bob", age: 30 });
381
382
console.log(state.userCache.get(1)); // { name: "Alice", age: 25 }
383
console.log(state.userCache.size); // 2
384
385
// Using object keys with ref
386
const objKey = ref({ id: "special" });
387
state.userCache.set(objKey, { name: "Special User", age: 35 });
388
389
// Without ref, object keys might not work as expected
390
const badKey = { id: "bad" };
391
state.userCache.set(badKey, { name: "Bad User", age: 40 });
392
console.log(state.userCache.get(badKey)); // undefined (key equality issue)
393
394
// Iteration
395
for (const [key, value] of state.userCache) {
396
console.log(`User ${key}:`, value.name);
397
}
398
399
// Type checking
400
console.log(isProxyMap(state.userCache)); // true
401
console.log(isProxyMap(new Map())); // false
402
```
403
404
## Utility Types
405
406
```typescript { .api }
407
type WatchCallback = (
408
get: <T extends object>(proxyObject: T) => T
409
) => void | (() => void) | Promise<void | (() => void)>;
410
411
type WatchOptions = {
412
sync?: boolean;
413
};
414
415
type Cleanup = () => void;
416
417
interface DevToolsOptions {
418
enabled?: boolean;
419
name?: string;
420
[key: string]: any;
421
}
422
423
// Extended collection interfaces
424
interface ProxySet<T> extends Set<T> {
425
// Extended set operations
426
intersection(other: Set<T>): Set<T>;
427
union(other: Set<T>): Set<T>;
428
difference(other: Set<T>): Set<T>;
429
symmetricDifference(other: Set<T>): Set<T>;
430
// Set comparison methods
431
isSubsetOf(other: Set<T>): boolean;
432
isSupersetOf(other: Set<T>): boolean;
433
isDisjointFrom(other: Set<T>): boolean;
434
// Additional methods
435
toJSON(): Set<T>;
436
}
437
438
interface ProxyMap<K, V> extends Map<K, V> {
439
toJSON(): Map<K, V>;
440
}
441
442
// Additional internal types
443
type AnyFunction = (...args: any[]) => any;
444
type ProxyObject = object;
445
type Path = (string | symbol)[];
446
```