or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-hooks.mdconcurrency-helpers.mdcore-hooks.mdfamily-patterns.mdindex.mdloadable-system.mdmemory-management.mdroot-provider.mdstate-definition.md

advanced-hooks.mddocs/

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