or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-atoms.mdindex.mdreact-integration.mdreact-utilities.mdvanilla-utilities.md
tile.json

react-utilities.mddocs/

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

```