0
# Advanced Hooks
1
2
Powerful hooks for complex operations including callbacks, transactions, snapshots, and state introspection. These hooks provide advanced capabilities for sophisticated state management patterns.
3
4
## Capabilities
5
6
### Recoil Callbacks
7
8
Hook for accessing Recoil state in event handlers and other non-render contexts.
9
10
```typescript { .api }
11
/**
12
* Returns a function that will run the callback that was passed when
13
* calling this hook. Useful for accessing Recoil state in response to
14
* events.
15
*/
16
function useRecoilCallback<Args extends ReadonlyArray<unknown>, Return>(
17
fn: (interface: CallbackInterface) => (...args: Args) => Return,
18
deps?: ReadonlyArray<unknown>
19
): (...args: Args) => Return;
20
21
interface CallbackInterface {
22
/** Set the value of writeable Recoil state */
23
set: <T>(recoilVal: RecoilState<T>, valOrUpdater: ((currVal: T) => T) | T) => void;
24
/** Reset Recoil state to its default value */
25
reset: (recoilVal: RecoilState<any>) => void;
26
/** Force refresh a selector by clearing its cache */
27
refresh: (recoilValue: RecoilValue<any>) => void;
28
/** Current snapshot of all Recoil state */
29
snapshot: Snapshot;
30
/** Update state to match the provided snapshot */
31
gotoSnapshot: (snapshot: Snapshot) => void;
32
/** Execute an atomic transaction */
33
transact_UNSTABLE: (cb: (i: TransactionInterface_UNSTABLE) => void) => void;
34
}
35
```
36
37
**Usage Examples:**
38
39
```typescript
40
import React from 'react';
41
import { useRecoilCallback, atom } from 'recoil';
42
43
const userState = atom({ key: 'userState', default: null });
44
const loadingState = atom({ key: 'loadingState', default: false });
45
46
// Event handler that updates multiple states
47
function useAuthActions() {
48
const login = useRecoilCallback(({set}) => async (credentials) => {
49
set(loadingState, true);
50
51
try {
52
const response = await fetch('/api/login', {
53
method: 'POST',
54
body: JSON.stringify(credentials),
55
});
56
const user = await response.json();
57
set(userState, user);
58
} catch (error) {
59
console.error('Login failed:', error);
60
} finally {
61
set(loadingState, false);
62
}
63
}, []);
64
65
const logout = useRecoilCallback(({reset, set}) => () => {
66
reset(userState);
67
set(loadingState, false);
68
}, []);
69
70
return { login, logout };
71
}
72
73
// Batch operations
74
function useBatchOperations() {
75
const updateMultipleItems = useRecoilCallback(({set}) => (updates) => {
76
// All updates happen in a single transaction
77
updates.forEach(({id, data}) => {
78
set(itemState(id), data);
79
});
80
}, []);
81
82
return { updateMultipleItems };
83
}
84
85
// Reading state in callbacks
86
function useDataSync() {
87
const syncData = useRecoilCallback(({snapshot}) => async () => {
88
const currentUser = await snapshot.getPromise(userState);
89
const currentData = await snapshot.getPromise(dataState);
90
91
await fetch('/api/sync', {
92
method: 'POST',
93
body: JSON.stringify({ user: currentUser, data: currentData }),
94
});
95
}, []);
96
97
return { syncData };
98
}
99
```
100
101
### Transactions (Unstable)
102
103
Hook for executing atomic state updates that are guaranteed to be consistent.
104
105
```typescript { .api }
106
/**
107
* Returns a function that executes an atomic transaction for updating Recoil state
108
*/
109
function useRecoilTransaction_UNSTABLE<Args extends ReadonlyArray<unknown>>(
110
fn: (interface: TransactionInterface_UNSTABLE) => (...args: Args) => void,
111
deps?: ReadonlyArray<unknown>
112
): (...args: Args) => void;
113
114
interface TransactionInterface_UNSTABLE {
115
/** Get the current value of Recoil state within the transaction */
116
get<T>(a: RecoilValue<T>): T;
117
/** Set the value of writeable Recoil state within the transaction */
118
set<T>(s: RecoilState<T>, u: ((currVal: T) => T) | T): void;
119
/** Reset Recoil state to its default value within the transaction */
120
reset(s: RecoilState<any>): void;
121
}
122
```
123
124
**Usage Examples:**
125
126
```typescript
127
import React from 'react';
128
import { useRecoilTransaction_UNSTABLE } from 'recoil';
129
130
// Atomic transfer between accounts
131
function useAccountOperations() {
132
const transferFunds = useRecoilTransaction_UNSTABLE(({get, set}) =>
133
(fromAccountId, toAccountId, amount) => {
134
const fromBalance = get(accountBalanceState(fromAccountId));
135
const toBalance = get(accountBalanceState(toAccountId));
136
137
if (fromBalance < amount) {
138
throw new Error('Insufficient funds');
139
}
140
141
// Both updates happen atomically
142
set(accountBalanceState(fromAccountId), fromBalance - amount);
143
set(accountBalanceState(toAccountId), toBalance + amount);
144
145
// Log the transaction
146
set(transactionLogState, (log) => [...log, {
147
type: 'transfer',
148
from: fromAccountId,
149
to: toAccountId,
150
amount,
151
timestamp: Date.now(),
152
}]);
153
}, []);
154
155
return { transferFunds };
156
}
157
158
// Complex state migration
159
function useStateMigration() {
160
const migrateToNewFormat = useRecoilTransaction_UNSTABLE(({get, set, reset}) => () => {
161
const oldData = get(legacyDataState);
162
const users = get(usersState);
163
164
// Reset old state
165
reset(legacyDataState);
166
167
// Transform and set new state
168
const transformedData = oldData.map(item => ({
169
id: item.legacyId,
170
userId: users.find(u => u.email === item.userEmail)?.id,
171
data: transformLegacyFormat(item.data),
172
migrated: true,
173
}));
174
175
set(newDataState, transformedData);
176
set(migrationStatusState, { completed: true, timestamp: Date.now() });
177
}, []);
178
179
return { migrateToNewFormat };
180
}
181
```
182
183
### Snapshot Management
184
185
Hooks for working with immutable snapshots of Recoil state.
186
187
```typescript { .api }
188
/**
189
* Returns a snapshot of the current Recoil state and subscribes the component
190
* to re-render when any state is updated
191
*/
192
function useRecoilSnapshot(): Snapshot;
193
194
/**
195
* Updates Recoil state to match the provided snapshot
196
*/
197
function useGotoRecoilSnapshot(): (snapshot: Snapshot) => void;
198
199
/**
200
* Observe all state transactions
201
*/
202
function useRecoilTransactionObserver_UNSTABLE(
203
callback: (opts: {
204
snapshot: Snapshot;
205
previousSnapshot: Snapshot;
206
}) => void
207
): void;
208
```
209
210
**Usage Examples:**
211
212
```typescript
213
import React, { useState } from 'react';
214
import {
215
useRecoilSnapshot,
216
useGotoRecoilSnapshot,
217
useRecoilTransactionObserver_UNSTABLE
218
} from 'recoil';
219
220
// Undo/Redo functionality
221
function useUndoRedo() {
222
const [history, setHistory] = useState([]);
223
const [historyIndex, setHistoryIndex] = useState(-1);
224
const snapshot = useRecoilSnapshot();
225
const gotoSnapshot = useGotoRecoilSnapshot();
226
227
// Save current state to history
228
const saveState = () => {
229
setHistory(prev => [...prev.slice(0, historyIndex + 1), snapshot]);
230
setHistoryIndex(prev => prev + 1);
231
};
232
233
const undo = () => {
234
if (historyIndex > 0) {
235
const previousSnapshot = history[historyIndex - 1];
236
gotoSnapshot(previousSnapshot);
237
setHistoryIndex(prev => prev - 1);
238
}
239
};
240
241
const redo = () => {
242
if (historyIndex < history.length - 1) {
243
const nextSnapshot = history[historyIndex + 1];
244
gotoSnapshot(nextSnapshot);
245
setHistoryIndex(prev => prev + 1);
246
}
247
};
248
249
return {
250
saveState,
251
undo,
252
redo,
253
canUndo: historyIndex > 0,
254
canRedo: historyIndex < history.length - 1,
255
};
256
}
257
258
// State debugging component
259
function StateDebugger() {
260
const snapshot = useRecoilSnapshot();
261
const [selectedNode, setSelectedNode] = useState(null);
262
263
const nodes = Array.from(snapshot.getNodes_UNSTABLE());
264
265
return (
266
<div>
267
<h3>Recoil State Debugger</h3>
268
<div style={{ display: 'flex' }}>
269
<div style={{ width: '50%' }}>
270
<h4>Atoms & Selectors</h4>
271
{nodes.map(node => (
272
<div
273
key={node.key}
274
onClick={() => setSelectedNode(node)}
275
style={{
276
cursor: 'pointer',
277
padding: '4px',
278
backgroundColor: selectedNode === node ? '#eee' : 'transparent'
279
}}
280
>
281
{node.key}
282
</div>
283
))}
284
</div>
285
286
<div style={{ width: '50%' }}>
287
{selectedNode && (
288
<div>
289
<h4>{selectedNode.key}</h4>
290
<pre>
291
{JSON.stringify(
292
snapshot.getLoadable(selectedNode).contents,
293
null,
294
2
295
)}
296
</pre>
297
</div>
298
)}
299
</div>
300
</div>
301
</div>
302
);
303
}
304
305
// Transaction logger
306
function TransactionLogger() {
307
const [transactions, setTransactions] = useState([]);
308
309
useRecoilTransactionObserver_UNSTABLE(({snapshot, previousSnapshot}) => {
310
const changedNodes = [];
311
312
for (const node of snapshot.getNodes_UNSTABLE()) {
313
const currentLoadable = snapshot.getLoadable(node);
314
const previousLoadable = previousSnapshot.getLoadable(node);
315
316
if (currentLoadable.state === 'hasValue' &&
317
previousLoadable.state === 'hasValue' &&
318
currentLoadable.contents !== previousLoadable.contents) {
319
changedNodes.push({
320
key: node.key,
321
from: previousLoadable.contents,
322
to: currentLoadable.contents,
323
});
324
}
325
}
326
327
if (changedNodes.length > 0) {
328
setTransactions(prev => [...prev, {
329
timestamp: Date.now(),
330
changes: changedNodes,
331
}]);
332
}
333
});
334
335
return (
336
<div>
337
<h3>Transaction Log</h3>
338
{transactions.map((transaction, i) => (
339
<div key={i}>
340
<strong>{new Date(transaction.timestamp).toLocaleTimeString()}</strong>
341
{transaction.changes.map((change, j) => (
342
<div key={j} style={{ marginLeft: '20px' }}>
343
{change.key}: {JSON.stringify(change.from)} → {JSON.stringify(change.to)}
344
</div>
345
))}
346
</div>
347
))}
348
</div>
349
);
350
}
351
```
352
353
### Snapshot Factory
354
355
Factory function for creating snapshots with initialized state.
356
357
```typescript { .api }
358
/**
359
* Factory to produce a Recoil snapshot object with all atoms in the default state
360
*/
361
function snapshot_UNSTABLE(initializeState?: (mutableSnapshot: MutableSnapshot) => void): Snapshot;
362
```
363
364
**Usage Examples:**
365
366
```typescript
367
import { snapshot_UNSTABLE, atom } from 'recoil';
368
369
const userState = atom({ key: 'userState', default: null });
370
const settingsState = atom({ key: 'settingsState', default: {} });
371
372
// Create snapshot with specific initial state
373
function createTestSnapshot() {
374
return snapshot_UNSTABLE(({set}) => {
375
set(userState, { id: 1, name: 'Test User' });
376
set(settingsState, { theme: 'dark', notifications: true });
377
});
378
}
379
380
// Use in testing
381
function TestComponent() {
382
const testSnapshot = createTestSnapshot();
383
const gotoSnapshot = useGotoRecoilSnapshot();
384
385
return (
386
<button onClick={() => gotoSnapshot(testSnapshot)}>
387
Load Test Data
388
</button>
389
);
390
}
391
392
// Export/Import state
393
function useStateExportImport() {
394
const snapshot = useRecoilSnapshot();
395
const gotoSnapshot = useGotoRecoilSnapshot();
396
397
const exportState = async () => {
398
const state = {};
399
for (const node of snapshot.getNodes_UNSTABLE()) {
400
const loadable = snapshot.getLoadable(node);
401
if (loadable.state === 'hasValue') {
402
state[node.key] = loadable.contents;
403
}
404
}
405
return JSON.stringify(state);
406
};
407
408
const importState = (stateJson) => {
409
const state = JSON.parse(stateJson);
410
const importedSnapshot = snapshot_UNSTABLE(({set}) => {
411
Object.entries(state).forEach(([key, value]) => {
412
// Find the atom/selector by key and set its value
413
const node = Array.from(snapshot.getNodes_UNSTABLE())
414
.find(n => n.key === key);
415
if (node) {
416
set(node, value);
417
}
418
});
419
});
420
gotoSnapshot(importedSnapshot);
421
};
422
423
return { exportState, importState };
424
}
425
```
426
427
## Advanced Patterns
428
429
**State Coordination:**
430
- Use callbacks for complex event handling that affects multiple atoms
431
- Use transactions for atomic updates that must be consistent
432
- Use snapshots for undo/redo, testing, and state persistence
433
434
**Performance Optimization:**
435
- Callbacks don't cause re-renders when dependencies change
436
- Transactions batch updates to minimize re-renders
437
- Snapshots are immutable and can be safely shared
438
439
**Debugging and Development:**
440
- Transaction observers for logging state changes
441
- Snapshot inspection for debugging complex state
442
- State export/import for development and testing workflows