@uniflowed/state 0.0.0-alpha.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,691 @@
1
+ // @flow
2
+ //
3
+ // `@uniflowed/state`: atoms, stores, and the React binding over them.
4
+ //
5
+ // # Where the line between this package and `@uniflowed/cell` is
6
+ //
7
+ // `@uniflowed/cell` is the reactive core: dependency tracking, memoisation,
8
+ // the equality cutoff, batching, glitch-free propagation. It has no opinion
9
+ // about React and no concept of a store.
10
+ //
11
+ // This package adds two things and nothing else:
12
+ //
13
+ // * an atom is a *definition* rather than a value, so the same declaration can
14
+ // have a different value in every store — which is what makes one process
15
+ // able to render two requests, and one page able to hold a draft of itself;
16
+ // * React, through `useSyncExternalStore`.
17
+ //
18
+ // There is no second dependency graph here. A derived atom is instantiated
19
+ // into a `derived` cell whose read is bound to a store, and every question
20
+ // about *when* it recomputes is answered one layer down. Two implementations
21
+ // of dependency tracking in one product is one too many, and the one that
22
+ // would rot is the copy.
23
+ //
24
+ // The vocabularies are close enough to be worth telling apart. One layer down,
25
+ // `state` and `derived` build cells that hold their own values; here, `atom`
26
+ // and `selector` build *definitions* that have a different value in every
27
+ // store. That is also why `read`, `write` and `subscribe` are spelled the same
28
+ // in both packages and take different arguments: an atom needs a store to be
29
+ // resolved against, and a cell is the value.
30
+ //
31
+ // # The file map
32
+ //
33
+ // * `internal/atom.js` — what an atom is before any store exists.
34
+ // * `internal/store.js` — where an atom's value lives, and how React reaches
35
+ // it.
36
+ // * `internal/composed.js` — atoms assembled out of other atoms: families,
37
+ // defaults, persistence.
38
+ // * `internal/provider.js` — which store a React subtree uses.
39
+ //
40
+ // This entry point is the vocabulary: the types an application writes down,
41
+ // the constructors it calls, and the four hooks. Every one of them is thin,
42
+ // because the machinery is behind them rather than in them.
43
+ //
44
+ // # Why the constructors are named rather than overloaded
45
+ //
46
+ // Jotai spells all of this `atom(...)` and decides what was meant from the
47
+ // arguments. That works in TypeScript, where the overload set is resolved at
48
+ // the call site; in Flow the honest version is an intersection of function
49
+ // types that the checker resolves poorly, and inference through composition is
50
+ // a stated requirement here. It is also ambiguous at runtime: `atom(f)` cannot
51
+ // distinguish a derived atom from a primitive one holding a function.
52
+ //
53
+ // So the four kinds are four names — `atom`, `selector`, `writableSelector`,
54
+ // `action` — and each returns exactly one type. In Jotai's terms they are
55
+ // `atom(value)`, `atom(read)`, `atom(read, write)` and `atom(null, write)`.
56
+ //
57
+ // # Why `useSyncExternalStore`
58
+ //
59
+ // A store is an external store, so the binding is the hook React provides for
60
+ // exactly that rather than a `useState` mirror kept in step by an effect. The
61
+ // difference is not stylistic: a mirror is written during commit, so a
62
+ // concurrent render can read one component's copy before another's has caught
63
+ // up, and the two disagree on screen. That is tearing, and this hook is the
64
+ // API that exists to prevent it.
65
+ //
66
+ // Nothing here mutates anything a render can observe, no hook returns a live
67
+ // mutable object, and the callbacks handed to React have an identity that does
68
+ // not change — see `internal/store.js` for why each of those matters.
69
+ //
70
+ // # What is deliberately not here
71
+ //
72
+ // `jotai/utils` is fifteen names and this package answers most of them. The
73
+ // ones it does not are listed here rather than left to be discovered, because
74
+ // "we thought about it and decided against" is worth more to somebody porting
75
+ // an application than silence is. ubugeeei-prod/uf#292 is the triage in full.
76
+ //
77
+ // * `atomWithLazy` — `atomWithDefault` already takes exactly that function.
78
+ // A second name for one thing is worse than one name for it.
79
+ // * `useHydrateAtoms` — hydrates the store the component is already in, on
80
+ // first render. That is a write to shared state during render, and
81
+ // `internal/store.js` sets out the conditions under which this package
82
+ // makes its one render-time write; this would meet none of them. The same
83
+ // job outside React is `createStore()`, `write(...)`, `<Provider store>`,
84
+ // which is one line longer and writes nothing during a render.
85
+ // * `atomFamily`'s `areEqual` — it turns an `O(1)` `Map` lookup into a linear
86
+ // scan of every key the family has ever seen, on a call that happens once
87
+ // per row per render. A key that is a tuple should be made a string;
88
+ // `family(\`${year}:${month}\`)` costs nothing and says what it costs.
89
+ // * `loadable` — deprecated upstream in favour of `unwrap`, which is here,
90
+ // and `asyncAtom` produces the `Loadable` shape directly rather than
91
+ // needing a wrapper to put it there.
92
+ // * `atomWithRefresh` — `refresh(target, store?)` is the same capability as a
93
+ // free function; the doc comment on it says why that shape was chosen.
94
+ // * `splitAtom` — an atom of one atom per row, each keeping its identity
95
+ // across an insert, a remove and a move. Not a stub and not a few lines:
96
+ // the row atoms have to be memoised somewhere, and a `selector`'s read is
97
+ // handed a `get` and nothing else, so there is no store to memoise them
98
+ // against. Every version of it either invents a per-store cache the read
99
+ // cannot reach or shares row atoms between stores and then has to answer
100
+ // what a row means in a store whose list does not contain it. That is a
101
+ // change to the store's model, and it does not belong inside a utility.
102
+ // * `atomWithObservable` — an atom fed by an `Observable` that may itself
103
+ // depend on other atoms. The half that does not is already `atom(initial,
104
+ // { onMount })`. The half that does needs a mount that re-runs when a
105
+ // dependency changes, and `onMount` is a subscription lifecycle: it runs
106
+ // for the first subscriber and stops for the last, and never in between.
107
+ // Offering it on a derived atom — which `internal/store.js` would already
108
+ // honour — would hand callers a `get` whose changes their subscription
109
+ // never hears about, which is worse than not offering it.
110
+
111
+ import type { Cell, LoadContext, Unsubscribe } from "@uniflowed/cell";
112
+ import * as React from "@uniflowed/react";
113
+ import { useSyncExternalStore } from "@uniflowed/react";
114
+
115
+ import type {
116
+ AtomOptions,
117
+ AtomRecord,
118
+ Loadable,
119
+ PrimitiveOptions,
120
+ SetAction,
121
+ } from "./internal/atom.js";
122
+ import { defineAction, defineAsync, definePrimitive, defineSelector } from "./internal/atom.js";
123
+ import type {
124
+ AsyncSetAction,
125
+ AsyncStorageAdapter,
126
+ AsyncStorageOptions,
127
+ Reset,
128
+ StorageAdapter,
129
+ StorageOptions,
130
+ } from "./internal/composed.js";
131
+ import {
132
+ RESET,
133
+ atomWithAsyncStorage as composeWithAsyncStorage,
134
+ atomWithDefault as composeWithDefault,
135
+ atomWithReducer as composeWithReducer,
136
+ atomWithReset as composeWithReset,
137
+ atomWithStorage as composeWithStorage,
138
+ freezeAtom as composeFreeze,
139
+ selectAtom as composeSelect,
140
+ unwrap as composeUnwrap,
141
+ } from "./internal/composed.js";
142
+ import type { StoreInstance } from "./internal/store.js";
143
+ import { bindCell, createStore as createStoreInstance, defaultStore } from "./internal/store.js";
144
+ import { StoreScope, useStoreInstance } from "./internal/provider.js";
145
+
146
+ export type {
147
+ AsyncSetAction,
148
+ AsyncStorageAdapter,
149
+ AsyncStorageOptions,
150
+ AtomFamily,
151
+ JSONStorageOptions,
152
+ Reset,
153
+ StorageAdapter,
154
+ StorageOptions,
155
+ StringStorage,
156
+ } from "./internal/composed.js";
157
+ export type {
158
+ AtomMount,
159
+ AtomOptions,
160
+ Loadable,
161
+ PrimitiveOptions,
162
+ SetAction,
163
+ } from "./internal/atom.js";
164
+ export type { Cell, LoadContext, Unsubscribe };
165
+
166
+ export { atomFamily, createJSONStorage, RESET } from "./internal/composed.js";
167
+ export { batch } from "@uniflowed/cell";
168
+
169
+ /**
170
+ * Any atom, as something to read.
171
+ *
172
+ * Every other atom type is a subtype of this one, so a function that only
173
+ * reads takes a `ReadonlyAtom<T>` and accepts all of them.
174
+ */
175
+ export opaque type ReadonlyAtom<T> = AtomRecord<T, empty>;
176
+
177
+ /**
178
+ * An atom that can be written with an argument of type `A`.
179
+ *
180
+ * `A` is not always the value: an atom holding a list may take an "add this
181
+ * one" argument, and keeping the two apart is what lets the setter a component
182
+ * is handed be typed exactly.
183
+ */
184
+ export opaque type WritableAtom<T, A>: ReadonlyAtom<T> = AtomRecord<T, A>;
185
+
186
+ /**
187
+ * A piece of state: readable, and writable the way `useState` is.
188
+ *
189
+ * The `useState`-shaped argument — a value, or a function of the current one —
190
+ * is the reason this is a type of its own rather than
191
+ * `WritableAtom<T, T>`: `setCount((n) => n + 1)` has to mean what it does
192
+ * everywhere else in React.
193
+ */
194
+ export opaque type Atom<T>: WritableAtom<T, SetAction<T>> = AtomRecord<T, SetAction<T>>;
195
+
196
+ /** An atom that is only ever written: an action. */
197
+ export opaque type WriteOnlyAtom<A>: WritableAtom<null, A> = AtomRecord<null, A>;
198
+
199
+ /**
200
+ * An atom whose value arrives from a promise: what [`asyncAtom`] returns.
201
+ *
202
+ * Nominally distinct from `ReadonlyAtom<Loadable<T>>` even though the two are
203
+ * the same record, and the distinction earns its place at exactly one call
204
+ * site: [`refresh`] can only mean something for an atom that has a load to
205
+ * run, and a `selector` that happens to return a `Loadable` — a cache lookup
206
+ * projected into one, say — has nothing to refresh. Without the name that
207
+ * mistake is a runtime error; with it, Flow says so at the call site.
208
+ */
209
+ export opaque type AsyncAtom<T>: ReadonlyAtom<Loadable<T>> = AtomRecord<Loadable<T>, empty>;
210
+
211
+ /** Reading another atom, inside a read or a write. */
212
+ export type Getter = <V>(target: ReadonlyAtom<V>) => V;
213
+
214
+ /** Writing another atom, inside a write. */
215
+ export type Setter = <V, A>(target: WritableAtom<V, A>, argument: A) => void;
216
+
217
+ /** What `useSetAtom` hands back for a `useState`-shaped atom. */
218
+ export type AtomSetter<T> = (next: SetAction<T>) => void;
219
+
220
+ /** What `useAtom` hands back: the shape `useState` returns. */
221
+ export type AtomTuple<T> = [T, AtomSetter<T>];
222
+
223
+ /**
224
+ * Where atom values live.
225
+ *
226
+ * Opaque, and deliberately small: `get`, `set` and `sub` are everything an
227
+ * application needs, and everything a test needs to assert on a tree's state
228
+ * without rendering one.
229
+ */
230
+ export opaque type Store = StoreInstance;
231
+
232
+ /**
233
+ * A piece of state.
234
+ *
235
+ * Declaring one allocates nothing and belongs to no store — the value appears
236
+ * the first time a store is asked for it. That is what makes it safe to
237
+ * declare atoms at module scope in a file a server imports.
238
+ *
239
+ * `onMount` runs when the first subscriber in a store arrives and its return
240
+ * value runs when the last one leaves, which is where a subscription to
241
+ * anything outside the graph belongs: a socket, an interval, a media query.
242
+ */
243
+ export function atom<T>(initial: T, options?: PrimitiveOptions<T>): Atom<T> {
244
+ return definePrimitive(initial, options);
245
+ }
246
+
247
+ /**
248
+ * State derived from other atoms, recomputed only when what it read changes.
249
+ *
250
+ * No dependency array: the read discovers its own dependencies by running, and
251
+ * they are rebuilt every time it runs. A read that branches —
252
+ * `get(showAll) ? get(all) : get(some)` — depends on the branch it took, so
253
+ * writing to the other one recomputes nothing.
254
+ *
255
+ * A read that returns an unchanged value does not re-render its readers, and
256
+ * `options.equals` is how a read that builds a fresh array each time says what
257
+ * "unchanged" means for it.
258
+ */
259
+ export function selector<T>(read: (get: Getter) => T, options?: AtomOptions<T>): ReadonlyAtom<T> {
260
+ return defineSelector(read, null, options);
261
+ }
262
+
263
+ /**
264
+ * A selector you can also write to.
265
+ *
266
+ * The write is given `get` and `set`, so it can decide what a change to this
267
+ * atom means in terms of the atoms it is derived from — a "full name" atom
268
+ * whose write splits into first and last, a filter atom that also resets the
269
+ * page number. Everything it sets happens in one batch, so subscribers to
270
+ * three of those atoms are woken once each rather than once per `set`.
271
+ *
272
+ * `get` inside a write does not create a dependency. A write is not a
273
+ * computation, and an atom that recomputed because a handler looked at
274
+ * something would be very hard to explain.
275
+ */
276
+ export function writableSelector<T, A>(
277
+ read: (get: Getter) => T,
278
+ write: (get: Getter, set: Setter, argument: A) => void,
279
+ options?: AtomOptions<T>,
280
+ ): WritableAtom<T, A> {
281
+ return defineSelector(read, write, options);
282
+ }
283
+
284
+ /**
285
+ * An atom that is only written: a named operation over a store.
286
+ *
287
+ * A component that dispatches one does not subscribe to anything, so it does
288
+ * not re-render when the state the action changes changes. That is the whole
289
+ * point of having it as an atom rather than a function: it is written where
290
+ * the state is, it can be replaced in a test by providing a different store,
291
+ * and dispatching it costs the caller no subscription.
292
+ */
293
+ export function action<A>(
294
+ write: (get: Getter, set: Setter, argument: A) => void,
295
+ options?: AtomOptions<null>,
296
+ ): WriteOnlyAtom<A> {
297
+ return defineAction(write, options);
298
+ }
299
+
300
+ /**
301
+ * A derived atom whose read is asynchronous.
302
+ *
303
+ * Its value is a [`Loadable`] — `loading`, `hasData` or `hasError` — rather
304
+ * than a promise a component suspends on. Suspense is not available to a
305
+ * `useSyncExternalStore` reader without throwing a promise from inside a
306
+ * snapshot, which is neither supported nor safe under concurrent rendering, so
307
+ * this package makes the loading state a value the caller renders rather than
308
+ * a control-flow trick. `unwrap` is there for callers that just want a
309
+ * fallback.
310
+ *
311
+ * The load is tracked: `asyncAtom((get) => fetchUser(get(userId)))` reloads
312
+ * when `userId` changes, and — this is the part that is hard to get right by
313
+ * hand — the load already in flight for the previous id is discarded rather
314
+ * than allowed to win a race and deliver the wrong user.
315
+ *
316
+ * Discarded, and also stopped. The load's second argument carries an
317
+ * `AbortSignal`, aborted when a newer load supersedes this one, when
318
+ * [`refresh`] asks for another, and when the atom loses its last subscriber:
319
+ *
320
+ * ```
321
+ * const user = asyncAtom((get, { signal }) =>
322
+ * fetch(`/users/${get(userId)}`, { signal }).then((response) => response.json()),
323
+ * );
324
+ * ```
325
+ *
326
+ * A load that ignores the signal is still correct — whether a result is
327
+ * adopted is decided by the store either way — but on a search box that
328
+ * reloads per keystroke, ignoring it is one live request per keystroke.
329
+ *
330
+ * An aborted load's rejection is not the atom's error: it never becomes
331
+ * `{ state: "hasError" }`, because it is the answer to a question the atom
332
+ * stopped asking.
333
+ */
334
+ export function asyncAtom<T>(
335
+ load: (get: Getter, context: LoadContext) => Promise<T>,
336
+ options?: AtomOptions<Loadable<T>>,
337
+ ): AsyncAtom<T> {
338
+ return defineAsync(load, options);
339
+ }
340
+
341
+ /**
342
+ * Load an asynchronous atom again, with the dependencies it already has.
343
+ *
344
+ * The ordinary case after a mutation, and behind the Retry button on the error
345
+ * state [`Loadable`] exists to make renderable. An `asyncAtom` otherwise
346
+ * reloads only when something it read changes, and writing a dependency the
347
+ * value it already holds is correctly dropped by the equality cutoff — so
348
+ * without this there is no way to say "ask again" at all.
349
+ *
350
+ * A free function rather than a write, which is the choice Jotai makes with
351
+ * `atomWithRefresh`. Two reasons, and the second is the one that decided it:
352
+ * `WritableAtom<T, A>` promises that `A` is the argument type, and an atom
353
+ * that is suddenly writable with no argument muddies that; and a free function
354
+ * works from a route handler, an event handler and a test, none of which have
355
+ * a component to hold a setter.
356
+ *
357
+ * The atom passes through `{ state: "loading" }` on the way, so a list that
358
+ * shows a spinner while it refetches gets one without asking.
359
+ */
360
+ export function refresh<T>(target: AsyncAtom<T>, store?: Store): void {
361
+ (store ?? defaultStore()).reload(target);
362
+ }
363
+
364
+ /**
365
+ * An atom whose value is computed until someone writes one, and again after
366
+ * [`RESET`].
367
+ */
368
+ export function atomWithDefault<T>(
369
+ getDefault: (get: Getter) => T,
370
+ options?: AtomOptions<T>,
371
+ ): WritableAtom<T, SetAction<T> | Reset> {
372
+ return composeWithDefault(getDefault, options);
373
+ }
374
+
375
+ /**
376
+ * A piece of state that also answers to [`RESET`].
377
+ *
378
+ * `atom(0)` cannot be reset — its argument type is `SetAction<number>` and
379
+ * nothing else — so this is what an application reaches for when "back to the
380
+ * default" is a thing the interface offers. It is [`atomWithDefault`] taking a
381
+ * value instead of a read, which is what a caller who has one already has.
382
+ */
383
+ export function atomWithReset<T>(
384
+ initial: T,
385
+ options?: AtomOptions<T>,
386
+ ): WritableAtom<T, SetAction<T> | Reset> {
387
+ return composeWithReset(initial, options);
388
+ }
389
+
390
+ /**
391
+ * State and the actions that change it: `useReducer`, as an atom.
392
+ *
393
+ * ```
394
+ * const count = atomWithReducer<number, "increment" | "reset">(0, (n, action) =>
395
+ * action === "increment" ? n + 1 : 0,
396
+ * );
397
+ * ```
398
+ *
399
+ * The value and the argument are different types, which is the reason to use
400
+ * this rather than an [`atom`]: `useSetAtom(count)` hands a component a
401
+ * dispatch that takes an action, so a state written to it by mistake is a
402
+ * type error rather than a state machine with a hole in it.
403
+ */
404
+ export function atomWithReducer<State, Action>(
405
+ initial: State,
406
+ reduce: (state: State, action: Action) => State,
407
+ options?: AtomOptions<State>,
408
+ ): WritableAtom<State, Action> {
409
+ return composeWithReducer(initial, reduce, options);
410
+ }
411
+
412
+ /**
413
+ * One part of another atom, so that a reader of the part is woken only when
414
+ * the part changes.
415
+ *
416
+ * ```
417
+ * const name = selectAtom(user, (current) => current.name);
418
+ * ```
419
+ *
420
+ * A component reading that re-renders when the name changes and not when
421
+ * anything else about the user does. `equals` is for a selection that builds a
422
+ * fresh value each time — an array of ids, a filtered list — which would
423
+ * otherwise be a new value on every recompute and wake every reader.
424
+ *
425
+ * Jotai's selector also takes the previous slice. This one does not: a read
426
+ * that can see its own output is not a pure function of its dependencies,
427
+ * which is what the equality cutoff underneath rests on, and `equals` is the
428
+ * direct way to say the thing that parameter was used to say.
429
+ */
430
+ export function selectAtom<T, Slice>(
431
+ source: ReadonlyAtom<T>,
432
+ select: (value: T) => Slice,
433
+ equals?: (previous: Slice, next: Slice) => boolean,
434
+ ): ReadonlyAtom<Slice> {
435
+ return composeSelect(source, select, equals);
436
+ }
437
+
438
+ /**
439
+ * The same atom, deeply frozen, so a mutation in place fails where it happens.
440
+ *
441
+ * `state.items.push(row)` does not replace the value, so the equality cutoff
442
+ * correctly reports no change and nothing re-renders — the most expensive
443
+ * beginner bug in this style of state, because the symptom appears in a
444
+ * component that is not the one at fault. Frozen, the push throws in a module
445
+ * and does nothing outside one, and either way it is at the line that did it.
446
+ *
447
+ * There is one object, not two: the value handed back is the value that came
448
+ * in, so the atom this derives from is frozen along with it. That is what
449
+ * makes the guard worth anything, and it is worth knowing before wrapping an
450
+ * atom other code writes to.
451
+ */
452
+ export function freezeAtom<T>(source: ReadonlyAtom<T>): ReadonlyAtom<T> {
453
+ return composeFreeze(source);
454
+ }
455
+
456
+ /**
457
+ * An atom mirrored into a key-value store on every write, and read back from
458
+ * it when a store mounts it.
459
+ *
460
+ * ```
461
+ * const theme = atomWithStorage("theme", "light", createJSONStorage(() => localStorage));
462
+ * ```
463
+ *
464
+ * The read happens on mount rather than when the atom is declared, and that is
465
+ * the whole difference between a persisted preference that survives hydration
466
+ * and one that does not: a server renders `initial`, so the first client
467
+ * render has to be `initial` too, and the stored value arrives immediately
468
+ * after commit. `options.getOnInit` is for an application with no server
469
+ * render to agree with.
470
+ *
471
+ * Writing [`RESET`] removes the key rather than storing the initial value,
472
+ * which is the difference between "back to the default" and "persisting the
473
+ * default forever".
474
+ */
475
+ export function atomWithStorage<T>(
476
+ key: string,
477
+ initial: T,
478
+ storage?: StorageAdapter<T>,
479
+ options?: StorageOptions<T>,
480
+ ): WritableAtom<T, SetAction<T> | Reset> {
481
+ return composeWithStorage(key, initial, storage, options);
482
+ }
483
+
484
+ /**
485
+ * An atom persisted to a storage whose read is asynchronous — IndexedDB, a
486
+ * React Native `AsyncStorage`, a preference store behind a request.
487
+ *
488
+ * ```
489
+ * const draft = atomWithAsyncStorage("draft", "", indexedDb);
490
+ * // { state: "loading" }, and then { state: "hasData", data: "…" }
491
+ * ```
492
+ *
493
+ * A second constructor rather than an option on [`atomWithStorage`], because
494
+ * the value is a different type. It is a [`Loadable`] until the first read
495
+ * settles, for the reason [`asyncAtom`] gives: a `useSyncExternalStore` reader
496
+ * cannot suspend, so a value that has not arrived is a state to render rather
497
+ * than a promise to throw. Jotai's `atomWithStorage` takes an async storage
498
+ * and makes the value `T | Promise<T>`; taking that signature without Suspense
499
+ * would leave the type promising a `T` that is not there.
500
+ *
501
+ * The setter takes a `T`, so the two type parameters differ. Its reducer form
502
+ * is handed the `Loadable`, not the value, because a write can happen before
503
+ * the first read has settled and there may be nothing to reduce.
504
+ *
505
+ * Three behaviours worth knowing before reaching for it, each of them a
506
+ * decision rather than a consequence:
507
+ *
508
+ * * a write while the first read is in flight wins, and the read is abandoned
509
+ * and its signal aborted — the load stops being a dependency, and the cell
510
+ * underneath decides the rest;
511
+ * * a read that *fails* is `{ state: "hasError" }` rather than `initial`,
512
+ * which is the whole reason this constructor can exist and the synchronous
513
+ * one cannot do it: "the database is locked" is not "nobody set a
514
+ * preference". An absent key is still `initial`;
515
+ * * a *write* that fails is silent. The value the caller wrote is the atom's
516
+ * value regardless; it simply will not outlive the session.
517
+ *
518
+ * [`RESET`] removes the key and puts the atom back to `initial` without
519
+ * reading again — a re-read would race the removal it has not waited for.
520
+ */
521
+ export function atomWithAsyncStorage<T>(
522
+ key: string,
523
+ initial: T,
524
+ storage: AsyncStorageAdapter<T>,
525
+ options?: AsyncStorageOptions<T>,
526
+ ): WritableAtom<Loadable<T>, AsyncSetAction<T> | Reset> {
527
+ return composeWithAsyncStorage(key, initial, storage, options);
528
+ }
529
+
530
+ /** The data an asynchronous atom is holding, or `fallback` until it has some. */
531
+ export function unwrap<T>(target: ReadonlyAtom<Loadable<T>>, fallback: T): ReadonlyAtom<T> {
532
+ return composeUnwrap(target, fallback);
533
+ }
534
+
535
+ /**
536
+ * A store of your own.
537
+ *
538
+ * One per request on a server, one per test that wants a clean slate, one per
539
+ * subtree that needs to disagree with the page around it.
540
+ */
541
+ export function createStore(): Store {
542
+ return createStoreInstance();
543
+ }
544
+
545
+ /** The store everything that does not name one uses. */
546
+ export function getDefaultStore(): Store {
547
+ return defaultStore();
548
+ }
549
+
550
+ /**
551
+ * Read an atom out of a store, or out of the default store.
552
+ *
553
+ * The same value a component would see, with no component: this is how a route
554
+ * handler, an event handler outside React, or a test reads state.
555
+ */
556
+ export function read<T>(target: ReadonlyAtom<T>, store?: Store): T {
557
+ return (store ?? defaultStore()).get(target);
558
+ }
559
+
560
+ /** Write an atom in a store, or in the default store. */
561
+ export function write<T, A>(target: WritableAtom<T, A>, argument: A, store?: Store): void {
562
+ (store ?? defaultStore()).set(target, argument);
563
+ }
564
+
565
+ /**
566
+ * Be told when an atom's value changes, outside React.
567
+ *
568
+ * Subscribing is also what mounts the atom, so an atom with an `onMount` is
569
+ * started by the first subscriber and stopped by the last — including when
570
+ * that subscriber is a component.
571
+ */
572
+ export function subscribe<T>(
573
+ target: ReadonlyAtom<T>,
574
+ listener: () => void,
575
+ store?: Store,
576
+ ): Unsubscribe {
577
+ return (store ?? defaultStore()).sub(target, listener);
578
+ }
579
+
580
+ /**
581
+ * Give a subtree its own store.
582
+ *
583
+ * With no `store`, the provider owns one it creates for itself, which is the
584
+ * shortest way to isolate a subtree — or a test — from everything else.
585
+ */
586
+ export component Provider(store?: Store, children: React.Node) {
587
+ return <StoreScope store={store ?? null}>{children}</StoreScope>;
588
+ }
589
+
590
+ /** The store this part of the tree reads: the scoped one, or the default. */
591
+ export hook useStore(store?: Store): Store {
592
+ return useStoreInstance(store);
593
+ }
594
+
595
+ /**
596
+ * Read an atom, re-rendering when — and only when — its value changes.
597
+ *
598
+ * A component that reads three atoms re-renders when any of the three changes
599
+ * and not when a fourth does, because the subscription is per atom rather than
600
+ * per store. A derived atom that recomputes to the value it already had does
601
+ * not re-render its readers at all.
602
+ */
603
+ export hook useAtomValue<T>(target: ReadonlyAtom<T>, store?: Store): T {
604
+ const bound = useStoreInstance(store).bind(target);
605
+ // The server snapshot is the same read: a store holds its value on both
606
+ // sides, so hydration compares like with like instead of a placeholder.
607
+ return useSyncExternalStore(bound.subscribe, bound.snapshot, bound.snapshot);
608
+ }
609
+
610
+ /**
611
+ * Write an atom without reading it.
612
+ *
613
+ * A component that only dispatches does not subscribe, so it does not
614
+ * re-render when the value it writes changes. The setter's identity is stable
615
+ * for as long as the store and the atom are, so passing it to a memoised child
616
+ * costs that child nothing.
617
+ */
618
+ export hook useSetAtom<T, A>(target: WritableAtom<T, A>, store?: Store): (argument: A) => void {
619
+ return useStoreInstance(store).bind(target).setter;
620
+ }
621
+
622
+ /** Read and write an atom, in the shape `useState` returns. */
623
+ export hook useAtom<T, A>(target: WritableAtom<T, A>, store?: Store): [T, (argument: A) => void] {
624
+ return [useAtomValue(target, store), useSetAtom(target, store)];
625
+ }
626
+
627
+ /**
628
+ * Put a resettable atom back, from a component.
629
+ *
630
+ * Jotai's `useResetAtom`. `useSetAtom(target)` already does this — the
631
+ * argument is [`RESET`] — and the difference is the shape of what comes back:
632
+ * `() => void` goes straight onto an `onClick`, where the setter needs a
633
+ * wrapper that supplies the symbol.
634
+ *
635
+ * It takes an atom whose write accepts `RESET` as well as a value, which is
636
+ * what [`atomWithReset`], [`atomWithDefault`] and [`atomWithStorage`] all
637
+ * return. A plain [`atom`] is refused, and refused at the call rather than
638
+ * with a runtime error, because its argument type does not include the symbol.
639
+ * [`atomWithAsyncStorage`]'s setter has a different value type, so it resets
640
+ * through `useSetAtom` — its own doc comment says so.
641
+ */
642
+ export hook useResetAtom<T>(
643
+ target: WritableAtom<T, SetAction<T> | Reset>,
644
+ store?: Store,
645
+ ): () => void {
646
+ const setter = useSetAtom<T, SetAction<T> | Reset>(target, store);
647
+ return () => {
648
+ setter(RESET);
649
+ };
650
+ }
651
+
652
+ /**
653
+ * A callback that can read and write any atom, and subscribes to none of them.
654
+ *
655
+ * Jotai's `useAtomCallback`. For the handler that needs the *current* value of
656
+ * something it does not render — a submit that reads a draft, an analytics
657
+ * call that reads a filter — where `useAtomValue` would re-render the
658
+ * component every time that value changed for a value it only ever looks at
659
+ * once.
660
+ *
661
+ * The `get` and `set` are the same pair a [`writableSelector`]'s write is
662
+ * given, resolved through the store this component is in. Reading through it
663
+ * records no dependency, because a handler runs outside any computation and
664
+ * there is nothing for one to be recorded against — which is the point: the
665
+ * component that holds this callback is subscribed to nothing.
666
+ *
667
+ * The identity of what comes back changes when `callback` does, so a handler
668
+ * written inline is a new function every render unless something memoizes it —
669
+ * which, inside a `component` the React Compiler compiled, it does. The same
670
+ * rule as every other hook that takes a function.
671
+ */
672
+ export hook useAtomCallback<Args extends $ReadOnlyArray<mixed>, Result>(
673
+ callback: (get: Getter, set: Setter, ...args: Args) => Result,
674
+ store?: Store,
675
+ ): (...args: Args) => Result {
676
+ const instance = useStoreInstance(store);
677
+ return (...args: Args) => callback(instance.get, instance.set, ...args);
678
+ }
679
+
680
+ /**
681
+ * Subscribe a component to a cell directly.
682
+ *
683
+ * The escape hatch to the layer below: a route loader hands out
684
+ * `@uniflowed/cell` cells, and a component that reads one should not have to
685
+ * wrap it in an atom to do so. A cell holds its own value, so no store is
686
+ * involved and the `store` argument the other hooks take would mean nothing.
687
+ */
688
+ export hook useCell<T>(source: Cell<T>): T {
689
+ const bound = bindCell(source);
690
+ return useSyncExternalStore(bound.subscribe, bound.snapshot, bound.snapshot);
691
+ }