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

vanilla-utilities.mddocs/

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

```