deno.land / x / jotai@v1.8.4 / docs / guides / async.mdx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
---title: Asyncdescription: This doc describes about the behavior with async.nav: 1.03---
Using async atoms, you gain access to real-world data while still managing them directly from your atoms and with incredible ease.
We can separate them in two main categories:
- Async read atoms: async request is started instantly as soon as you try to get its value. You could relate to them as "smart getters".- Async write atoms: async request is started at a specific moment. You could relate to them as "actions".## Async read atom
The `read` function of an atom can return a promise.
```jsconst countAtom = atom(1)const asyncAtom = atom(async (get) => get(countAtom) * 2)```
Jotai is inherently leveraging `Suspense` to handle asynchronous flows.
```jsxconst ComponentUsingAsyncAtoms = () => { const [num] = useAtom(asyncAtom) // here `num` is always `number` even though asyncAtom returns a Promise}const App = () => { return ( <Suspense fallback={/* What to show while suspended */}> <ComponentUsingAsyncAtoms /> </Suspense> )}```
Alternatively, you could avoid the inherent suspending that Jotai does for you, by wrapping your atoms with the [`loadable` API](../utils/loadable.mdx).
**Note**: An atom becomes async not only if the atom read function is async,but also if one or more of its dependencies are async.
```jsconst anotherAtom = atom((get) => get(asyncAtom) / 2)// even though this atom doesn't return a promise,// it's a read async atom because `asyncAtom` is async.```
**Note**: You cannot get the value of an async atom in a write-atom (whether the write function is async or not) before its value has been resolved
```jsconst asyncAtom = atom(async (get) => ...)const writeAtom = atom(null, (get, set, payload) => { get(asyncAtom) // This throws an error if "asyncAtom" is still in pending state})```
If you want to make sure the action will never fail, you could preload atoms at root level of your app directly:
```jsxconst Preloader = () => { useAtomValue(asyncAtom) // The value will be pre-loaded return null}
const Root = () => { return ( <Suspense fallback={<Text>Loading...<Text>}> <Preloader /> {/* Wait for atoms to preload */} <App /> {/* Rest of your app */} </Suspense> )}```
## Async write atom
Async write atoms are another kind of async atom. When the `write` function of atom returns a promise.
```jsconst countAtom = atom(1)const asyncIncrementAtom = atom(null, async (get, set) => { // await something set(countAtom, get(countAtom) + 1)})
const Component = () => { const [, increment] = useAtom(asyncIncrementAtom) const handleClick = () => { increment() } // ...}```
## Async sometimes
An interesting pattern that can be achieved with Jotai are is switching from async to sync to trigger suspending when wanted.
```jsconst request = async () => fetch('https://...').then((res) => res.json())const baseAtom = atom(0)const Component = () => { const [value, setValue] = useAtom(baseAtom) const handleClick = () => { setValue(request()) // Will suspend until request resolves } // ...}```
## Async forever
Sometimes you may want to suspend until an unpredetermined moment (or never).
```jsconst baseAtom = atom(new Promise(() => {})) // Will be suspend until set otherwise```
## Suspense
Async support is first class in Jotai. It fully leverages React Suspense at its core.
> Technically, Suspense usage other than React.lazy is still unsupported / undocumented in React 17. If this is blocking, so you can still use the [`loadable` API](../utils/loadable.mdx) to avoid suspendingTo use async atoms, you need to wrap your component tree with `<Suspense>`.
> If you have a `<Provider>`, place **at least one** `<Suspense>` inside said `<Provider>`; otherwise, it may cause an endless loop while rendering the components.```jsxconst App = () => ( <Provider> <Suspense fallback="Loading..."> <Layout /> </Suspense> </Provider>)```
Having more `<Suspense>`s in the component tree is also possible and must be considered to profit from Jotai inherent handling at best.
<details><summary>Async write atom behavior until v1.3.9</summary>(This is no longer the case since v1.4.0.)
### Triggering `Suspense` fallback of write atom
This section applies only for "async write atom" not "async read atom", which works differently with `Suspense`.
**A write atom will trigger the `Suspense` fallback if:**
* the atom's write argument (2nd one) is async * the awaited call is made directly, and not from inside another containing functionThis _will_ trigger the `Suspense` fallback:
```tsconst writeAtom = atom(null, async (get, set) => { const response = await new Promise<string>((resolve, _reject) => { setTimeout(() => { resolve('some returned value') }, 2000) }) set(somePrimitiveAtom, 'The returned value is: ' + response)})```
This _will not_ trigger the `Suspense` fallback:
```tsconst writeAtom = atom(null, (get, set) => { const getResponse = async () => { const response = await new Promise<string>((resolve, _reject) => { setTimeout(() => { resolve('some returned value') }, 2000) }) set(somePrimitiveAtom, 'The returned value is: ' + response) } getResponse()})```
But **_both_** of the above will still set `somePrimitiveAtom` to the correct values.
</details>
jotai

Version Info

Tagged at
a year ago