Skip to content

Commit f7fb8d2

Browse files
committed
Mention loadable and withPending in README.md
1 parent 0db9329 commit f7fb8d2

File tree

2 files changed

+86
-18
lines changed

2 files changed

+86
-18
lines changed

README.md

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,18 +53,20 @@ const filteredPetsAtom = eagerAtom((get) => {
5353
```
5454

5555
Now, the type reflects the eager behavior of this atom.
56-
It's value will be `string[]` if the only thing that
56+
Its value will be `string[]` if the only thing that
5757
changed is the filter, and `Promise<string[]>` otherwise!
5858

59-
> Codesandbox example of jotai-eager + React:
59+
> CodeSandbox example of jotai-eager + React:
6060
>
6161
> [![Explore jotai-eager example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/devbox/jotai-derive-example-forked-pf38dg)
6262
63+
In addition to eager atoms, jotai-eager provides `loadable` for consistent loading state handling and `withPending` for fallback values during async resolution.
64+
6365
## Recipes
6466

6567
### Avoiding request waterfalls
6668

67-
If your atom has multiple async dependencies, best to jump start all of them at once and wait for their results, instead of awaiting them sequentially. In vanilla async atoms, `Promise.all(...)` is the API to use, but in eager atoms, use the `get.all()` API:
69+
If your atom has multiple async dependencies, it's best to initiate all of them simultaneously and wait for their results, instead of awaiting them sequentially. In vanilla async atoms, `Promise.all(...)` is the API to use, but in eager atoms, use the `get.all()` API:
6870

6971
```ts
7072
const myMessages = eagerAtom((get) => {
@@ -75,7 +77,7 @@ const myMessages = eagerAtom((get) => {
7577

7678
### Awaiting a Promise that is not another atom's value
7779

78-
We can use the `get.await` API to await regular Promises inside `eagerAtom` definitions, granted we make sure that the Promise
80+
We can use the `get.await` API to await regular Promises inside `eagerAtom` definitions, as long as we make sure that the Promise
7981
we're passing is consistent across invocations of the atom's read function.
8082

8183
```ts
@@ -87,11 +89,43 @@ const statusAtom = eagerAtom((get) => {
8789
});
8890
```
8991

92+
### Handling Loading States with `loadable`
93+
94+
The `loadable` API wraps an atom to provide a consistent loading state representation, sharing a Promise cache between all jotai-eager APIs to minimize suspensions.
95+
96+
```ts
97+
import { atom } from 'jotai';
98+
import { loadable } from 'jotai-eager';
99+
100+
const asyncAtom = atom(async () => 'data');
101+
const loadableAtom = loadable(asyncAtom);
102+
103+
// Use in component:
104+
const state = useAtom(loadableAtom);
105+
if (state.state === 'loading') return <div>Loading...</div>;
106+
if (state.state === 'hasError') return <div>Error: {state.error}</div>;
107+
return <div>{state.data}</div>;
108+
```
109+
110+
### Handling Pending States with `withPending`
111+
112+
The `withPending` API wraps an atom to handle unresolved values by returning a fallback, providing an alternative to Jotai's `unwrap` with enhanced pending state management.
113+
114+
```ts
115+
import { atom } from 'jotai';
116+
import { withPending } from 'jotai-eager';
117+
118+
const asyncAtom = atom(Promise.resolve('data'));
119+
const wrappedAtom = withPending(asyncAtom, () => 'Loading...');
120+
121+
// Returns 'Loading...' while pending, then 'data'
122+
```
123+
90124
## Caveats
91125

92126
### Using `try` & `catch` inside eager atoms
93127

94-
Eager atoms internally use exceptions to "suspend" computation of the atom until an async dependency is fulfilled (similar to React's suspense behavior, but does not require React to work). This means that using exception handling inside of eager atoms has to be instrumented with an additional call to `isEagerError`.
128+
Eager atoms internally use exceptions to suspend computation of the atom until an async dependency is fulfilled (similar to React's suspense behavior, but does not require React to function). This means that using exception handling inside eager atoms has to be instrumented with an additional call to `isEagerError`.
95129

96130
```ts
97131
import { eagerAtom, isEagerError } from 'jotai-eager';
@@ -112,8 +146,8 @@ const fooAtom = eagerAtom((get) => {
112146

113147
### Awaiting a Promise that is created inside the atom
114148

115-
Since the read function is "retried" after a Promise we await is fulfilled, the mechanism expects
116-
the same promise to be passed into `get.await` the second time around. Since we're creating the
149+
Since the read function is 'retried' after a Promise we await is fulfilled, the mechanism expects
150+
the same promise to be passed into `get.await` the second time around. Since we are creating the
117151
Promise inside of the read function itself, that will never be the case, and we'll be stuck in an infinite loop.
118152

119153
```ts
@@ -126,7 +160,7 @@ const deferredNumberAtom = eagerAtom((get) => {
126160
});
127161
```
128162

129-
For this particular use-case, since we're always deferring, using an `eagerAtom` over
163+
For this particular use case, since we're always deferring, using an `eagerAtom` over
130164
a vanilla async atom is unnecessary. [See Advanced Usage for more complex patterns](#advanced-usage).
131165

132166
### Make note of the dual nature
@@ -181,5 +215,5 @@ immediately.
181215

182216
Building data graphs with these dual-natured (sometimes async, sometimes sync) atoms as a base can lead to unnecessary rerenders, stale values and micro-suspensions (in case of React) if not handled with care.
183217

184-
`jotai-eager` provides a primitive for building asynchronous data graphs
218+
`jotai-eager` provides primitives for building asynchronous data graphs
185219
that act on values as soon as they are available (either awaiting for them, or acting on them synchronously).

packages/jotai-eager/README.md

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,20 @@ const filteredPetsAtom = eagerAtom((get) => {
5555
```
5656

5757
Now, the type reflects the eager behavior of this atom.
58-
It's value will be `string[]` if the only thing that
58+
Its value will be `string[]` if the only thing that
5959
changed is the filter, and `Promise<string[]>` otherwise!
6060

61-
> Codesandbox example of jotai-eager + React:
61+
> CodeSandbox example of jotai-eager + React:
6262
>
6363
> [![Explore jotai-eager example](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/p/devbox/jotai-derive-example-forked-pf38dg)
6464
65+
In addition to eager atoms, jotai-eager provides `loadable` for consistent loading state handling and `withPending` for fallback values during async resolution.
66+
6567
## Recipes
6668

6769
### Avoiding request waterfalls
6870

69-
If your atom has multiple async dependencies, best to jump start all of them at once and wait for their results, instead of awaiting them sequentially. In vanilla async atoms, `Promise.all(...)` is the API to use, but in eager atoms, use the `get.all()` API:
71+
If your atom has multiple async dependencies, it's best to initiate all of them simultaneously and wait for their results, instead of awaiting them sequentially. In vanilla async atoms, `Promise.all(...)` is the API to use, but in eager atoms, use the `get.all()` API:
7072

7173
```ts
7274
const myMessages = eagerAtom((get) => {
@@ -77,7 +79,7 @@ const myMessages = eagerAtom((get) => {
7779

7880
### Awaiting a Promise that is not another atom's value
7981

80-
We can use the `get.await` API to await regular Promises inside `eagerAtom` definitions, granted we make sure that the Promise
82+
We can use the `get.await` API to await regular Promises inside `eagerAtom` definitions, as long as we make sure that the Promise
8183
we're passing is consistent across invocations of the atom's read function.
8284

8385
```ts
@@ -89,11 +91,43 @@ const statusAtom = eagerAtom((get) => {
8991
});
9092
```
9193

94+
### Handling Loading States with `loadable`
95+
96+
The `loadable` API wraps an atom to provide a consistent loading state representation, sharing a Promise cache between all jotai-eager APIs to minimize suspensions.
97+
98+
```ts
99+
import { atom } from 'jotai';
100+
import { loadable } from 'jotai-eager';
101+
102+
const asyncAtom = atom(async () => 'data');
103+
const loadableAtom = loadable(asyncAtom);
104+
105+
// Use in component:
106+
const state = useAtom(loadableAtom);
107+
if (state.state === 'loading') return <div>Loading...</div>;
108+
if (state.state === 'hasError') return <div>Error: {state.error}</div>;
109+
return <div>{state.data}</div>;
110+
```
111+
112+
### Handling Pending States with `withPending`
113+
114+
The `withPending` API wraps an atom to handle unresolved values by returning a fallback, providing an alternative to Jotai's `unwrap` with enhanced pending state management.
115+
116+
```ts
117+
import { atom } from 'jotai';
118+
import { withPending } from 'jotai-eager';
119+
120+
const asyncAtom = atom(Promise.resolve('data'));
121+
const wrappedAtom = withPending(asyncAtom, () => 'Loading...');
122+
123+
// Returns 'Loading...' while pending, then 'data'
124+
```
125+
92126
## Caveats
93127

94128
### Using `try` & `catch` inside eager atoms
95129

96-
Eager atoms internally use exceptions to "suspend" computation of the atom until an async dependency is fulfilled (similar to React's suspense behavior, but does not require React to work). This means that using exception handling inside of eager atoms has to be instrumented with an additional call to `isEagerError`.
130+
Eager atoms internally use exceptions to suspend computation of the atom until an async dependency is fulfilled (similar to React's suspense behavior, but does not require React to function). This means that using exception handling inside eager atoms has to be instrumented with an additional call to `isEagerError`.
97131

98132
```ts
99133
import { eagerAtom, isEagerError } from 'jotai-eager';
@@ -114,8 +148,8 @@ const fooAtom = eagerAtom((get) => {
114148

115149
### Awaiting a Promise that is created inside the atom
116150

117-
Since the read function is "retried" after a Promise we await is fulfilled, the mechanism expects
118-
the same promise to be passed into `get.await` the second time around. Since we're creating the
151+
Since the read function is 'retried' after a Promise we await is fulfilled, the mechanism expects
152+
the same promise to be passed into `get.await` the second time around. Since we are creating the
119153
Promise inside of the read function itself, that will never be the case, and we'll be stuck in an infinite loop.
120154

121155
```ts
@@ -128,7 +162,7 @@ const deferredNumberAtom = eagerAtom((get) => {
128162
});
129163
```
130164

131-
For this particular use-case, since we're always deferring, using an `eagerAtom` over
165+
For this particular use case, since we're always deferring, using an `eagerAtom` over
132166
a vanilla async atom is unnecessary. [See Advanced Usage for more complex patterns](#advanced-usage).
133167

134168
### Make note of the dual nature
@@ -183,7 +217,7 @@ immediately.
183217

184218
Building data graphs with these dual-natured (sometimes async, sometimes sync) atoms as a base can lead to unnecessary rerenders, stale values and micro-suspensions (in case of React) if not handled with care.
185219

186-
`jotai-eager` provides a primitive for building asynchronous data graphs
220+
`jotai-eager` provides primitives for building asynchronous data graphs
187221
that act on values as soon as they are available (either awaiting for them, or acting on them synchronously).
188222

189223
<!-- /automd -->

0 commit comments

Comments
 (0)