0
# React Utilities
1
2
React-specific utility hooks providing advanced patterns and functionality for Jotai atoms in React applications. These utilities complement the core React integration with specialized hooks for common use cases.
3
4
## Capabilities
5
6
### Reset Utilities
7
8
#### useResetAtom Hook
9
10
Hook for resetting atoms created with atomWithReset.
11
12
```typescript { .api }
13
/**
14
* Hook for resetting atoms created with atomWithReset
15
* @param anAtom - WritableAtom that accepts RESET
16
* @param options - Optional configuration
17
* @returns Reset function that resets the atom to its initial value
18
*/
19
function useResetAtom<T>(
20
anAtom: WritableAtom<unknown, [typeof RESET], T>,
21
options?: Options
22
): () => T;
23
24
interface Options {
25
store?: Store;
26
}
27
```
28
29
**Usage Examples:**
30
31
```typescript
32
import { atomWithReset, RESET } from "jotai/utils";
33
import { useResetAtom } from "jotai/utils";
34
35
const countAtom = atomWithReset(0);
36
37
function Counter() {
38
const [count, setCount] = useAtom(countAtom);
39
const resetCount = useResetAtom(countAtom);
40
41
return (
42
<div>
43
<p>Count: {count}</p>
44
<button onClick={() => setCount((c) => c + 1)}>+</button>
45
<button onClick={() => setCount((c) => c - 1)}>-</button>
46
<button onClick={resetCount}>Reset</button>
47
</div>
48
);
49
}
50
51
// With custom store
52
function CounterWithCustomStore() {
53
const customStore = createStore();
54
const resetCount = useResetAtom(countAtom, { store: customStore });
55
56
return <button onClick={resetCount}>Reset</button>;
57
}
58
```
59
60
### Reducer Utilities
61
62
#### useReducerAtom Hook (Deprecated)
63
64
Hook for using reducer pattern with atoms.
65
66
```typescript { .api }
67
/**
68
* @deprecated Use atomWithReducer and useAtom instead
69
* Hook for using reducer pattern with atoms
70
* @param anAtom - PrimitiveAtom to apply reducer to
71
* @param reducer - Reducer function
72
* @param options - Optional configuration
73
* @returns Tuple of [value, dispatch function]
74
*/
75
function useReducerAtom<Value, Action>(
76
anAtom: PrimitiveAtom<Value>,
77
reducer: (value: Value, action: Action) => Value,
78
options?: Options
79
): [Value, (action: Action) => void];
80
```
81
82
**Usage Examples:**
83
84
```typescript
85
import { useReducerAtom } from "jotai/utils";
86
87
const countAtom = atom(0);
88
89
type CountAction =
90
| { type: "increment" }
91
| { type: "decrement" }
92
| { type: "set"; value: number };
93
94
const countReducer = (count: number, action: CountAction) => {
95
switch (action.type) {
96
case "increment":
97
return count + 1;
98
case "decrement":
99
return count - 1;
100
case "set":
101
return action.value;
102
default:
103
return count;
104
}
105
};
106
107
function Counter() {
108
// Note: This is deprecated, use atomWithReducer instead
109
const [count, dispatch] = useReducerAtom(countAtom, countReducer);
110
111
return (
112
<div>
113
<p>Count: {count}</p>
114
<button onClick={() => dispatch({ type: "increment" })}>+</button>
115
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
116
<button onClick={() => dispatch({ type: "set", value: 0 })}>Reset</button>
117
</div>
118
);
119
}
120
```
121
122
### Callback Utilities
123
124
#### useAtomCallback Hook
125
126
Hook for creating callbacks that can read and write atoms.
127
128
```typescript { .api }
129
/**
130
* Hook for creating callbacks that can read and write atoms
131
* @param callback - Callback function with get and set access
132
* @param options - Optional configuration
133
* @returns Memoized callback function
134
*/
135
function useAtomCallback<Result, Args extends unknown[]>(
136
callback: (get: Getter, set: Setter, ...args: Args) => Result,
137
options?: Options
138
): (...args: Args) => Result;
139
140
/**
141
* Hook for creating async callbacks that can read and write atoms
142
* @param callback - Async callback function with get and set access
143
* @param options - Optional configuration
144
* @returns Memoized async callback function
145
*/
146
function useAtomCallback<Result, Args extends unknown[]>(
147
callback: (get: Getter, set: Setter, ...args: Args) => Promise<Result>,
148
options?: Options
149
): (...args: Args) => Promise<Result>;
150
151
type Getter = <Value>(atom: Atom<Value>) => Value;
152
type Setter = <Value, Args extends unknown[], Result>(
153
atom: WritableAtom<Value, Args, Result>,
154
...args: Args
155
) => Result;
156
```
157
158
**Usage Examples:**
159
160
```typescript
161
import { useAtomCallback } from "jotai/utils";
162
163
const countAtom = atom(0);
164
const userAtom = atom({ name: "Alice", score: 0 });
165
166
function GameComponent() {
167
const incrementScore = useAtomCallback(
168
(get, set) => {
169
const currentCount = get(countAtom);
170
const currentUser = get(userAtom);
171
172
set(countAtom, currentCount + 1);
173
set(userAtom, {
174
...currentUser,
175
score: currentUser.score + currentCount
176
});
177
}
178
);
179
180
const resetGame = useAtomCallback(
181
(get, set) => {
182
set(countAtom, 0);
183
set(userAtom, (prev) => ({ ...prev, score: 0 }));
184
}
185
);
186
187
// Callback with parameters
188
const addPoints = useAtomCallback(
189
(get, set, points: number) => {
190
const currentUser = get(userAtom);
191
set(userAtom, {
192
...currentUser,
193
score: currentUser.score + points
194
});
195
}
196
);
197
198
return (
199
<div>
200
<button onClick={incrementScore}>Increment Score</button>
201
<button onClick={() => addPoints(10)}>Add 10 Points</button>
202
<button onClick={resetGame}>Reset Game</button>
203
</div>
204
);
205
}
206
207
// Async callback example
208
function DataComponent() {
209
const loadUserData = useAtomCallback(
210
async (get, set, userId: string) => {
211
try {
212
const response = await fetch(`/api/users/${userId}`);
213
const userData = await response.json();
214
set(userAtom, userData);
215
return userData;
216
} catch (error) {
217
console.error("Failed to load user data:", error);
218
throw error;
219
}
220
}
221
);
222
223
const handleLoadUser = async () => {
224
try {
225
const user = await loadUserData("123");
226
console.log("Loaded user:", user);
227
} catch (error) {
228
console.error("Error:", error);
229
}
230
};
231
232
return (
233
<button onClick={handleLoadUser}>Load User</button>
234
);
235
}
236
```
237
238
### Hydration Utilities
239
240
#### useHydrateAtoms Hook
241
242
Hook for SSR hydration of atom values, supporting various data structures.
243
244
```typescript { .api }
245
/**
246
* Hook for hydrating atoms with values (array form)
247
* @param values - Array of [atom, value] tuples
248
* @param options - Optional configuration
249
*/
250
function useHydrateAtoms<Values extends readonly (readonly [Atom<unknown>, unknown])[]>(
251
values: Values,
252
options?: Options
253
): void;
254
255
/**
256
* Hook for hydrating atoms with values (Map form)
257
* @param values - Map of atoms to values
258
* @param options - Optional configuration
259
*/
260
function useHydrateAtoms<Values extends Map<Atom<unknown>, unknown>>(
261
values: Values,
262
options?: Options
263
): void;
264
265
/**
266
* Hook for hydrating atoms with values (Iterable form)
267
* @param values - Iterable of [atom, value] tuples
268
* @param options - Optional configuration
269
*/
270
function useHydrateAtoms<Values extends Iterable<readonly [Atom<unknown>, unknown]>>(
271
values: Values,
272
options?: Options
273
): void;
274
```
275
276
**Usage Examples:**
277
278
```typescript
279
import { useHydrateAtoms } from "jotai/utils";
280
281
const userAtom = atom({ name: "", email: "" });
282
const themeAtom = atom("light");
283
const countAtom = atom(0);
284
285
// Array form
286
function App({ initialUserData, initialTheme, initialCount }) {
287
useHydrateAtoms([
288
[userAtom, initialUserData],
289
[themeAtom, initialTheme],
290
[countAtom, initialCount]
291
]);
292
293
return <MainContent />;
294
}
295
296
// Map form
297
function AppWithMap({ serverData }) {
298
const hydrateMap = new Map([
299
[userAtom, serverData.user],
300
[themeAtom, serverData.theme],
301
[countAtom, serverData.count]
302
]);
303
304
useHydrateAtoms(hydrateMap);
305
306
return <MainContent />;
307
}
308
309
// Custom store
310
function AppWithCustomStore({ serverData }) {
311
const customStore = createStore();
312
313
useHydrateAtoms([
314
[userAtom, serverData.user],
315
[themeAtom, serverData.theme]
316
], { store: customStore });
317
318
return (
319
<Provider store={customStore}>
320
<MainContent />
321
</Provider>
322
);
323
}
324
325
// Next.js SSR example
326
function MyApp({ Component, pageProps }) {
327
return (
328
<Provider>
329
<HydrateAtoms {...pageProps}>
330
<Component {...pageProps} />
331
</HydrateAtoms>
332
</Provider>
333
);
334
}
335
336
function HydrateAtoms({ children, ...props }) {
337
useHydrateAtoms([
338
[userAtom, props.user],
339
[themeAtom, props.theme]
340
]);
341
return children;
342
}
343
344
export async function getServerSideProps() {
345
const user = await fetchUser();
346
const theme = await fetchTheme();
347
348
return {
349
props: {
350
user,
351
theme
352
}
353
};
354
}
355
```
356
357
### TypeScript Utilities
358
359
#### INTERNAL_InferAtomTuples Type
360
361
Internal type utility for inferring atom tuple types.
362
363
```typescript { .api }
364
/**
365
* Internal type for inferring atom tuple types
366
* Used internally by useHydrateAtoms for type inference
367
*/
368
type INTERNAL_InferAtomTuples<T> = T extends readonly (readonly [infer A, infer V])[]
369
? A extends Atom<infer AV>
370
? readonly (readonly [A, AV])[]
371
: never
372
: never;
373
```
374
375
## Advanced Usage Patterns
376
377
### Combining Multiple Utilities
378
379
```typescript
380
import {
381
atomWithReset,
382
useResetAtom,
383
useAtomCallback,
384
useHydrateAtoms
385
} from "jotai/utils";
386
387
const gameStateAtom = atomWithReset({
388
score: 0,
389
level: 1,
390
lives: 3,
391
isPlaying: false
392
});
393
394
function GameManager({ initialGameState }) {
395
// Hydrate with server data
396
useHydrateAtoms([[gameStateAtom, initialGameState]]);
397
398
const resetGame = useResetAtom(gameStateAtom);
399
400
const advanceLevel = useAtomCallback((get, set) => {
401
const currentState = get(gameStateAtom);
402
set(gameStateAtom, {
403
...currentState,
404
level: currentState.level + 1,
405
score: currentState.score + 1000
406
});
407
});
408
409
const gameOver = useAtomCallback((get, set) => {
410
const currentState = get(gameStateAtom);
411
set(gameStateAtom, {
412
...currentState,
413
isPlaying: false,
414
lives: 0
415
});
416
});
417
418
return (
419
<div>
420
<button onClick={resetGame}>New Game</button>
421
<button onClick={advanceLevel}>Next Level</button>
422
<button onClick={gameOver}>Game Over</button>
423
</div>
424
);
425
}
426
```
427
428
### Custom Hook Patterns
429
430
```typescript
431
import { useAtomCallback, useAtomValue } from "jotai/utils";
432
433
// Custom hook for managing shopping cart
434
function useShoppingCart() {
435
const items = useAtomValue(cartItemsAtom);
436
const totalPrice = useAtomValue(cartTotalAtom);
437
438
const addItem = useAtomCallback((get, set, product: Product) => {
439
const currentItems = get(cartItemsAtom);
440
const existingItem = currentItems.find(item => item.id === product.id);
441
442
if (existingItem) {
443
set(cartItemsAtom, currentItems.map(item =>
444
item.id === product.id
445
? { ...item, quantity: item.quantity + 1 }
446
: item
447
));
448
} else {
449
set(cartItemsAtom, [...currentItems, { ...product, quantity: 1 }]);
450
}
451
});
452
453
const removeItem = useAtomCallback((get, set, productId: string) => {
454
const currentItems = get(cartItemsAtom);
455
set(cartItemsAtom, currentItems.filter(item => item.id !== productId));
456
});
457
458
const clearCart = useAtomCallback((get, set) => {
459
set(cartItemsAtom, []);
460
});
461
462
return {
463
items,
464
totalPrice,
465
addItem,
466
removeItem,
467
clearCart
468
};
469
}
470
471
// Usage in component
472
function ShoppingCart() {
473
const { items, totalPrice, addItem, removeItem, clearCart } = useShoppingCart();
474
475
return (
476
<div>
477
<h2>Cart (${totalPrice})</h2>
478
{items.map(item => (
479
<div key={item.id}>
480
{item.name} x {item.quantity}
481
<button onClick={() => removeItem(item.id)}>Remove</button>
482
</div>
483
))}
484
<button onClick={clearCart}>Clear Cart</button>
485
</div>
486
);
487
}
488
```
489
490
### SSR and Hydration Patterns
491
492
```typescript
493
import { useHydrateAtoms } from "jotai/utils";
494
495
// Universal data loading pattern
496
function UniversalApp({ ssrData }) {
497
// Hydrate atoms with SSR data
498
useHydrateAtoms([
499
[userAtom, ssrData.user],
500
[settingsAtom, ssrData.settings],
501
[notificationsAtom, ssrData.notifications]
502
]);
503
504
return <AppContent />;
505
}
506
507
// Conditional hydration based on environment
508
function ConditionalHydration({ children, serverData }) {
509
const hydrationData = useMemo(() => {
510
if (typeof window === 'undefined') {
511
// Server-side: no hydration needed
512
return [];
513
}
514
515
// Client-side: hydrate with server data
516
return [
517
[userAtom, serverData.user],
518
[sessionAtom, serverData.session]
519
];
520
}, [serverData]);
521
522
useHydrateAtoms(hydrationData);
523
524
return children;
525
}
526
527
// Progressive hydration
528
function ProgressiveHydration({ criticalData, deferredData, children }) {
529
// Immediate hydration for critical data
530
useHydrateAtoms([
531
[userAtom, criticalData.user],
532
[authAtom, criticalData.auth]
533
]);
534
535
// Deferred hydration for non-critical data
536
useEffect(() => {
537
const timer = setTimeout(() => {
538
// Hydrate non-critical data after initial render
539
if (deferredData) {
540
// Manual store updates for deferred data
541
const store = getDefaultStore();
542
store.set(preferencesAtom, deferredData.preferences);
543
store.set(analyticsAtom, deferredData.analytics);
544
}
545
}, 0);
546
547
return () => clearTimeout(timer);
548
}, [deferredData]);
549
550
return children;
551
}
552
```