0
# Vanilla Utilities
1
2
Core utility functions providing advanced atom patterns and functionality. These utilities extend the basic atom functionality with common patterns like storage persistence, family management, async operations, and more.
3
4
## Capabilities
5
6
### Reset Utilities
7
8
#### RESET Constant
9
10
Special symbol used to reset atom values to their initial state.
11
12
```typescript { .api }
13
const RESET: unique symbol;
14
```
15
16
#### atomWithReset Function
17
18
Creates a resettable atom that can be reset to its initial value using the RESET symbol.
19
20
```typescript { .api }
21
/**
22
* Creates a resettable atom that can be reset to initial value
23
* @param initialValue - The initial value for the atom
24
* @returns WritableAtom that accepts SetStateAction or RESET
25
*/
26
function atomWithReset<Value>(
27
initialValue: Value
28
): WritableAtom<Value, [SetStateAction<Value> | typeof RESET], void>;
29
30
type SetStateActionWithReset<Value> = SetStateAction<Value> | typeof RESET;
31
```
32
33
**Usage Examples:**
34
35
```typescript
36
import { atomWithReset, RESET } from "jotai/utils";
37
38
const countAtom = atomWithReset(0);
39
40
// In component
41
const [count, setCount] = useAtom(countAtom);
42
43
// Normal updates
44
setCount(5);
45
setCount((prev) => prev + 1);
46
47
// Reset to initial value
48
setCount(RESET);
49
```
50
51
### Reducer Utilities
52
53
#### atomWithReducer Function
54
55
Creates an atom using the reducer pattern for state updates.
56
57
```typescript { .api }
58
/**
59
* Creates an atom using reducer pattern for state updates
60
* @param initialValue - The initial value for the atom
61
* @param reducer - Reducer function that takes current value and action
62
* @returns WritableAtom that accepts actions for state updates
63
*/
64
function atomWithReducer<Value, Action>(
65
initialValue: Value,
66
reducer: (value: Value, action: Action) => Value
67
): WritableAtom<Value, [Action], void>;
68
```
69
70
**Usage Examples:**
71
72
```typescript
73
import { atomWithReducer } from "jotai/utils";
74
75
type CountAction =
76
| { type: "increment" }
77
| { type: "decrement" }
78
| { type: "set"; value: number };
79
80
const countReducer = (count: number, action: CountAction) => {
81
switch (action.type) {
82
case "increment":
83
return count + 1;
84
case "decrement":
85
return count - 1;
86
case "set":
87
return action.value;
88
default:
89
return count;
90
}
91
};
92
93
const countAtom = atomWithReducer(0, countReducer);
94
95
// In component
96
const [count, dispatch] = useAtom(countAtom);
97
98
// Dispatch actions
99
dispatch({ type: "increment" });
100
dispatch({ type: "set", value: 10 });
101
```
102
103
### Family Utilities
104
105
#### atomFamily Function
106
107
Creates atom families for dynamic atom creation based on parameters.
108
109
```typescript { .api }
110
/**
111
* Creates atom families for dynamic atom creation based on parameters
112
* @param initializeAtom - Function to create atoms based on parameters
113
* @param areEqual - Optional equality function for parameter comparison
114
* @returns AtomFamily instance with parameter-based atom management
115
*/
116
function atomFamily<Param, AtomType>(
117
initializeAtom: (param: Param) => AtomType,
118
areEqual?: (a: Param, b: Param) => boolean
119
): AtomFamily<Param, AtomType>;
120
121
interface AtomFamily<Param, AtomType> {
122
(param: Param): AtomType;
123
getParams(): Param[];
124
remove(param: Param): void;
125
setShouldRemove(shouldRemove: (createdAt: number, param: Param) => boolean): void;
126
unstable_listen(callback: (event: AtomFamilyEvent<Param>) => void): () => void;
127
}
128
129
type AtomFamilyEvent<Param> =
130
| { type: "CREATE"; param: Param }
131
| { type: "REMOVE"; param: Param };
132
```
133
134
**Usage Examples:**
135
136
```typescript
137
import { atomFamily } from "jotai/utils";
138
139
// Simple atom family
140
const todoAtomFamily = atomFamily((id: number) =>
141
atom({ id, text: "", completed: false })
142
);
143
144
// Usage
145
const todo1Atom = todoAtomFamily(1);
146
const todo2Atom = todoAtomFamily(2);
147
148
// Custom equality function
149
const userAtomFamily = atomFamily(
150
(user: { id: number; name: string }) => atom(user),
151
(a, b) => a.id === b.id
152
);
153
154
// Family management
155
const allParams = todoAtomFamily.getParams(); // Get all created parameters
156
todoAtomFamily.remove(1); // Remove atom for parameter 1
157
158
// Auto-cleanup based on time
159
todoAtomFamily.setShouldRemove((createdAt, param) => {
160
return Date.now() - createdAt > 60000; // Remove after 1 minute
161
});
162
```
163
164
### Selection Utilities
165
166
#### selectAtom Function
167
168
Creates derived atoms with selector functions and optional equality comparison.
169
170
```typescript { .api }
171
/**
172
* Creates derived atom with selector function and optional equality comparison
173
* @param anAtom - Source atom to select from
174
* @param selector - Function to transform the atom value
175
* @param equalityFn - Optional function to compare selected values
176
* @returns Derived atom with selected value
177
*/
178
function selectAtom<Value, Slice>(
179
anAtom: Atom<Value>,
180
selector: (value: Value, prevSlice?: Slice) => Slice,
181
equalityFn?: (a: Slice, b: Slice) => boolean
182
): Atom<Slice>;
183
```
184
185
**Usage Examples:**
186
187
```typescript
188
import { selectAtom } from "jotai/utils";
189
190
const userAtom = atom({
191
id: 1,
192
name: "Alice",
193
email: "alice@example.com",
194
preferences: { theme: "dark", notifications: true }
195
});
196
197
// Select specific fields
198
const userNameAtom = selectAtom(userAtom, (user) => user.name);
199
const userPreferencesAtom = selectAtom(userAtom, (user) => user.preferences);
200
201
// With equality function to prevent unnecessary re-renders
202
const userDisplayAtom = selectAtom(
203
userAtom,
204
(user) => ({ name: user.name, email: user.email }),
205
(a, b) => a.name === b.name && a.email === b.email
206
);
207
```
208
209
### Immutability Utilities
210
211
#### freezeAtom Function
212
213
Makes atom values immutable using Object.freeze.
214
215
```typescript { .api }
216
/**
217
* Makes atom values immutable using Object.freeze
218
* @param anAtom - Atom to make immutable
219
* @returns Atom with frozen values
220
*/
221
function freezeAtom<AtomType>(anAtom: AtomType): AtomType;
222
```
223
224
#### freezeAtomCreator Function (Deprecated)
225
226
Creates immutable atoms from atom creator functions.
227
228
```typescript { .api }
229
/**
230
* @deprecated Use freezeAtom instead
231
* Creates immutable atoms from atom creator functions
232
*/
233
function freezeAtomCreator<Args extends unknown[], AtomType>(
234
createAtom: (...args: Args) => AtomType
235
): (...args: Args) => AtomType;
236
```
237
238
**Usage Examples:**
239
240
```typescript
241
import { freezeAtom } from "jotai/utils";
242
243
const mutableDataAtom = atom({ count: 0, items: [] });
244
const immutableDataAtom = freezeAtom(mutableDataAtom);
245
246
// Values are frozen and cannot be mutated
247
const [data, setData] = useAtom(immutableDataAtom);
248
// data is frozen, cannot modify data.count directly
249
```
250
251
### Array Utilities
252
253
#### splitAtom Function
254
255
Splits array atoms into individual item atoms for granular updates.
256
257
```typescript { .api }
258
/**
259
* Splits array atom into individual item atoms (read-only)
260
* @param arrAtom - Array atom to split
261
* @returns Atom containing array of individual item atoms
262
*/
263
function splitAtom<Item>(
264
arrAtom: Atom<Item[]>
265
): Atom<Atom<Item>[]>;
266
267
/**
268
* Splits writable array atom into individual writable item atoms
269
* @param arrAtom - Writable array atom to split
270
* @returns Atom containing array of writable item atoms with remove capability
271
*/
272
function splitAtom<Item>(
273
arrAtom: WritableAtom<Item[], [SetStateAction<Item[]>], void>
274
): Atom<WritableAtom<Item, [SetStateAction<Item>], void>[]>;
275
276
/**
277
* Splits array atom with custom key extraction for item identification
278
* @param arrAtom - Array atom to split
279
* @param keyExtractor - Function to extract unique keys from items
280
* @returns Atom containing array of keyed item atoms
281
*/
282
function splitAtom<Item, Key>(
283
arrAtom: WritableAtom<Item[], [SetStateAction<Item[]>], void>,
284
keyExtractor: (item: Item) => Key
285
): Atom<WritableAtom<Item, [SetStateAction<Item>], void>[]>;
286
```
287
288
**Usage Examples:**
289
290
```typescript
291
import { splitAtom } from "jotai/utils";
292
293
const todosAtom = atom([
294
{ id: 1, text: "Learn Jotai", completed: false },
295
{ id: 2, text: "Build app", completed: false }
296
]);
297
298
const todoAtomsAtom = splitAtom(todosAtom);
299
300
// In component
301
function TodoList() {
302
const todoAtoms = useAtomValue(todoAtomsAtom);
303
304
return (
305
<div>
306
{todoAtoms.map((todoAtom, index) => (
307
<TodoItem key={index} todoAtom={todoAtom} />
308
))}
309
</div>
310
);
311
}
312
313
function TodoItem({ todoAtom }: { todoAtom: WritableAtom<Todo, [SetStateAction<Todo>], void> }) {
314
const [todo, setTodo] = useAtom(todoAtom);
315
316
return (
317
<div>
318
<input
319
type="checkbox"
320
checked={todo.completed}
321
onChange={(e) => setTodo((prev) => ({ ...prev, completed: e.target.checked }))}
322
/>
323
<span>{todo.text}</span>
324
</div>
325
);
326
}
327
```
328
329
### Default Value Utilities
330
331
#### atomWithDefault Function
332
333
Creates atoms with computed default values.
334
335
```typescript { .api }
336
/**
337
* Creates atom with computed default value
338
* @param getDefault - Function to compute the default value
339
* @returns WritableAtom with computed default
340
*/
341
function atomWithDefault<Value>(
342
getDefault: Read<Value>
343
): WritableAtom<Value, [SetStateAction<Value> | typeof RESET], void>;
344
```
345
346
**Usage Examples:**
347
348
```typescript
349
import { atomWithDefault, RESET } from "jotai/utils";
350
351
const configAtom = atom({ apiUrl: "https://api.example.com", timeout: 5000 });
352
353
const apiUrlAtom = atomWithDefault((get) => get(configAtom).apiUrl);
354
const timeoutAtom = atomWithDefault((get) => get(configAtom).timeout);
355
356
// Can be used normally
357
const [apiUrl, setApiUrl] = useAtom(apiUrlAtom);
358
setApiUrl("https://api-staging.example.com");
359
360
// Reset to computed default
361
setApiUrl(RESET); // Recomputes from configAtom
362
```
363
364
### Storage Utilities
365
366
#### atomWithStorage Function
367
368
Creates persistent atoms backed by storage.
369
370
```typescript { .api }
371
/**
372
* Creates persistent atom backed by synchronous storage
373
* @param key - Storage key
374
* @param initialValue - Initial value if not in storage
375
* @param storage - Optional storage implementation
376
* @returns WritableAtom with storage persistence
377
*/
378
function atomWithStorage<Value>(
379
key: string,
380
initialValue: Value,
381
storage?: SyncStorage<Value>
382
): WritableAtom<Value, [SetStateAction<Value>], void>;
383
384
/**
385
* Creates persistent atom backed by asynchronous storage
386
* @param key - Storage key
387
* @param initialValue - Initial value if not in storage
388
* @param storage - Async storage implementation
389
* @returns WritableAtom with async storage persistence
390
*/
391
function atomWithStorage<Value>(
392
key: string,
393
initialValue: Value,
394
storage: AsyncStorage<Value>
395
): WritableAtom<Value, [SetStateAction<Value>], void>;
396
```
397
398
#### createJSONStorage Function
399
400
Creates JSON-based storage adapters.
401
402
```typescript { .api }
403
/**
404
* Creates default JSON storage adapter (uses localStorage)
405
* @returns JSON storage adapter using localStorage
406
*/
407
function createJSONStorage<Value>(): SyncStorage<Value>;
408
409
/**
410
* Creates sync JSON storage adapter from string storage
411
* @param getStringStorage - Function returning sync string storage
412
* @param options - Optional JSON parsing/stringifying options
413
* @returns Sync JSON storage adapter
414
*/
415
function createJSONStorage<Value>(
416
getStringStorage: () => SyncStringStorage,
417
options?: JsonStorageOptions
418
): SyncStorage<Value>;
419
420
/**
421
* Creates async JSON storage adapter from string storage
422
* @param getStringStorage - Function returning async string storage
423
* @param options - Optional JSON parsing/stringifying options
424
* @returns Async JSON storage adapter
425
*/
426
function createJSONStorage<Value>(
427
getStringStorage: () => AsyncStringStorage,
428
options?: JsonStorageOptions
429
): AsyncStorage<Value>;
430
```
431
432
#### withStorageValidator Function (unstable)
433
434
Adds validation to storage operations. Exported as `unstable_withStorageValidator`.
435
436
```typescript { .api }
437
/**
438
* Adds validation to storage operations
439
* @param validator - Type guard function for validation
440
* @returns Function that wraps storage with validation
441
*/
442
function withStorageValidator<Value>(
443
validator: (value: unknown) => value is Value
444
): <S extends SyncStorage<any> | AsyncStorage<any>>(
445
storage: S
446
) => S extends SyncStorage<any> ? SyncStorage<Value> : AsyncStorage<Value>;
447
```
448
449
**Usage Examples:**
450
451
```typescript
452
import { atomWithStorage, createJSONStorage, unstable_withStorageValidator } from "jotai/utils";
453
454
// Basic localStorage usage
455
const darkModeAtom = atomWithStorage("darkMode", false);
456
457
// Custom storage
458
const customStorage = createJSONStorage(() => sessionStorage);
459
const sessionAtom = atomWithStorage("session", null, customStorage);
460
461
// With validation
462
const isUser = (value: unknown): value is { id: number; name: string } =>
463
typeof value === "object" && value !== null &&
464
typeof (value as any).id === "number" &&
465
typeof (value as any).name === "string";
466
467
const validatedStorage = unstable_withStorageValidator(isUser)(
468
createJSONStorage(() => localStorage)
469
);
470
471
const userAtom = atomWithStorage("user", { id: 0, name: "" }, validatedStorage);
472
```
473
474
### Storage Interfaces
475
476
```typescript { .api }
477
type Subscribe<Value> = (
478
key: string,
479
callback: (value: Value) => void,
480
initialValue: Value
481
) => (() => void) | undefined;
482
483
type StringSubscribe = (
484
key: string,
485
callback: (value: string | null) => void
486
) => (() => void) | undefined;
487
488
interface SyncStorage<Value> {
489
getItem: (key: string, initialValue: Value) => Value;
490
setItem: (key: string, value: Value) => void;
491
removeItem: (key: string) => void;
492
subscribe?: Subscribe<Value>;
493
}
494
495
interface AsyncStorage<Value> {
496
getItem: (key: string, initialValue: Value) => Promise<Value>;
497
setItem: (key: string, value: Value) => Promise<void>;
498
removeItem: (key: string) => Promise<void>;
499
subscribe?: Subscribe<Value>;
500
}
501
502
interface SyncStringStorage {
503
getItem: (key: string) => string | null;
504
setItem: (key: string, value: string) => void;
505
removeItem: (key: string) => void;
506
subscribe?: StringSubscribe;
507
}
508
509
interface AsyncStringStorage {
510
getItem: (key: string) => Promise<string | null>;
511
setItem: (key: string, value: string) => Promise<void>;
512
removeItem: (key: string) => Promise<void>;
513
subscribe?: StringSubscribe;
514
}
515
516
type JsonStorageOptions = {
517
reviver?: (key: string, value: unknown) => unknown;
518
replacer?: (key: string, value: unknown) => unknown;
519
};
520
```
521
522
### Observable Utilities
523
524
#### atomWithObservable Function
525
526
Creates atoms from observables (RxJS, etc.).
527
528
```typescript { .api }
529
/**
530
* Creates atom from observable
531
* @param createObservable - Function that creates observable
532
* @returns Atom that follows observable values
533
*/
534
function atomWithObservable<Value>(
535
createObservable: () => Observable<Value>
536
): Atom<Value>;
537
538
/**
539
* Creates atom from observable with initial value
540
* @param createObservable - Function that creates observable
541
* @param initialValue - Initial value before observable emits
542
* @returns Atom that follows observable values
543
*/
544
function atomWithObservable<Value>(
545
createObservable: () => Observable<Value>,
546
initialValue: Value
547
): Atom<Value>;
548
549
interface Observable<Value> {
550
subscribe(observer: {
551
next: (value: Value) => void;
552
error?: (error: any) => void;
553
complete?: () => void;
554
}): { unsubscribe: () => void };
555
}
556
```
557
558
**Usage Examples:**
559
560
```typescript
561
import { atomWithObservable } from "jotai/utils";
562
import { interval } from "rxjs";
563
import { map } from "rxjs/operators";
564
565
// Simple observable atom
566
const clockAtom = atomWithObservable(() =>
567
interval(1000).pipe(map(() => new Date()))
568
);
569
570
// With initial value
571
const countdownAtom = atomWithObservable(
572
() => interval(1000).pipe(map(i => 10 - i)),
573
10
574
);
575
```
576
577
### Async Utilities
578
579
#### loadable Function
580
581
Wraps atoms in loadable state for async operations.
582
583
```typescript { .api }
584
/**
585
* Wraps atom in loadable state for async operations
586
* @param anAtom - Atom to wrap
587
* @returns Atom with loadable state
588
*/
589
function loadable<Value>(anAtom: Atom<Value>): Atom<Loadable<Value>>;
590
591
type Loadable<Value> =
592
| { state: "loading" }
593
| { state: "hasError"; error: unknown }
594
| { state: "hasData"; data: Value };
595
```
596
597
#### unwrap Function
598
599
Unwraps promise-based atoms with optional fallback values.
600
601
```typescript { .api }
602
/**
603
* Unwraps promise-based atom with fallback value
604
* @param anAtom - Promise-based atom to unwrap
605
* @param fallback - Fallback value while promise is pending
606
* @returns Atom with unwrapped value
607
*/
608
function unwrap<Value>(
609
anAtom: Atom<Promise<Value>>,
610
fallback: Value
611
): Atom<Value>;
612
613
/**
614
* Unwraps promise-based atom, throwing on pending
615
* @param anAtom - Promise-based atom to unwrap
616
* @returns Atom with unwrapped value
617
*/
618
function unwrap<Value>(anAtom: Atom<Promise<Value>>): Atom<Value>;
619
```
620
621
**Usage Examples:**
622
623
```typescript
624
import { loadable, unwrap } from "jotai/utils";
625
626
const asyncDataAtom = atom(async () => {
627
const response = await fetch("/api/data");
628
return response.json();
629
});
630
631
// Using loadable
632
const loadableDataAtom = loadable(asyncDataAtom);
633
634
function DataComponent() {
635
const loadableData = useAtomValue(loadableDataAtom);
636
637
if (loadableData.state === "loading") {
638
return <div>Loading...</div>;
639
}
640
641
if (loadableData.state === "hasError") {
642
return <div>Error: {loadableData.error.message}</div>;
643
}
644
645
return <div>Data: {JSON.stringify(loadableData.data)}</div>;
646
}
647
648
// Using unwrap with fallback
649
const unwrappedDataAtom = unwrap(asyncDataAtom, []);
650
651
function SimpleDataComponent() {
652
const data = useAtomValue(unwrappedDataAtom);
653
return <div>Data: {JSON.stringify(data)}</div>;
654
}
655
```
656
657
### Refresh Utilities
658
659
#### atomWithRefresh Function
660
661
Creates refreshable atoms that can be manually refreshed by calling the setter with no arguments.
662
663
```typescript { .api }
664
/**
665
* Creates refreshable read-only atom
666
* @param read - Read function for the atom
667
* @returns WritableAtom that refreshes when called with no arguments
668
*/
669
function atomWithRefresh<Value>(
670
read: Read<Value, [], void>
671
): WritableAtom<Value, [], void>;
672
673
/**
674
* Creates refreshable writable atom
675
* @param read - Read function for the atom
676
* @param write - Write function for the atom
677
* @returns WritableAtom that refreshes when called with no arguments, otherwise uses write function
678
*/
679
function atomWithRefresh<Value, Args extends unknown[], Result>(
680
read: Read<Value, Args, Result>,
681
write: Write<Value, Args, Result>
682
): WritableAtom<Value, Args | [], Result | void>;
683
```
684
685
**Usage Examples:**
686
687
```typescript
688
import { atomWithRefresh } from "jotai/utils";
689
690
const dataAtom = atomWithRefresh(async () => {
691
const response = await fetch("/api/data");
692
return response.json();
693
});
694
695
function DataComponent() {
696
const data = useAtomValue(dataAtom);
697
const refreshData = useSetAtom(dataAtom);
698
699
return (
700
<div>
701
<div>Data: {JSON.stringify(data)}</div>
702
<button onClick={() => refreshData()}>Refresh</button>
703
</div>
704
);
705
}
706
707
// Writable refreshable atom
708
const userAtom = atomWithRefresh(
709
async () => {
710
const response = await fetch("/api/user");
711
return response.json();
712
},
713
async (get, set, newUser: User) => {
714
await fetch("/api/user", {
715
method: "PUT",
716
body: JSON.stringify(newUser)
717
});
718
// Refresh after update
719
set(userAtom);
720
}
721
);
722
723
function UserComponent() {
724
const [user, setUser] = useAtom(userAtom);
725
726
return (
727
<div>
728
<div>User: {user.name}</div>
729
<button onClick={() => setUser()}>Refresh User</button>
730
<button onClick={() => setUser({ ...user, name: "New Name" })}>
731
Update User
732
</button>
733
</div>
734
);
735
}
736
```
737
738
### Lazy Utilities
739
740
#### atomWithLazy Function
741
742
Creates lazily initialized atoms.
743
744
```typescript { .api }
745
/**
746
* Creates lazily initialized atom
747
* @param makeInitial - Function to create initial value
748
* @returns PrimitiveAtom with lazy initialization
749
*/
750
function atomWithLazy<Value>(makeInitial: () => Value): PrimitiveAtom<Value>;
751
```
752
753
**Usage Examples:**
754
755
```typescript
756
import { atomWithLazy } from "jotai/utils";
757
758
// Expensive computation only runs when first accessed
759
const expensiveAtom = atomWithLazy(() => {
760
console.log("Computing expensive value...");
761
return Array.from({ length: 1000000 }, (_, i) => i);
762
});
763
764
// Initial value is computed lazily
765
const timestampAtom = atomWithLazy(() => Date.now());
766
```