Skip to content

Commit 4c60795

Browse files
committed
Add guide to docs.
1 parent a5bb323 commit 4c60795

File tree

8 files changed

+323
-1
lines changed

8 files changed

+323
-1
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ Use it with `fetch`, Axios or other data fetching libraries, even GraphQL.
112112
- [State properties](https://docs.react-async.com/api/state)
113113
- [Helper components](https://docs.react-async.com/api/helpers)
114114

115+
## Guide
116+
117+
- [Async components](https://docs.react-async.com/guide/async-components)
118+
- [Separating view and logic](https://docs.react-async.com/guide/separating-view-logic)
119+
- [Async actions](https://docs.react-async.com/guide/async-actions)
120+
- [Optimistic updates](https://docs.react-async.com/guide/optimistic-updates)
121+
- [Server-side rendering](https://docs.react-async.com/guide/server-side-rendering)
122+
115123
# Contributors
116124

117125
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):

docs/_summary.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@
1515
- [Configuration options](api/options.md)
1616
- [State properties](api/state.md)
1717
- [Helper components](api/helpers.md)
18+
19+
## Guide
20+
21+
- [Async components](guide/async-components.md)
22+
- [Separating view and logic](guide/separating-view-logic.md)
23+
- [Async actions](guide/async-actions.md)
24+
- [Optimistic updates](guide/optimistic-updates.md)
25+
- [Server-side rendering](guide/server-side-rendering.md)

docs/api/interfaces.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
React Async provides several ways to use it. The classic interface is through the `<Async>` component, which is
44
backwards compatible to React v16.3. More recent React applications will be using hooks, of which two are provided:
5-
`useAsync` and `useFetch`. Functionally, `<Async>` and `useAsync` are equivalent. `useFetch` is a special type of `useAsync` which is tied to the native `fetch` API.
5+
`useAsync` and `useFetch`. Functionally, `<Async>` and `useAsync` are equivalent. `useFetch` is a special version of
6+
`useAsync` which is tied to the native `fetch` API.
67

78
React Async accepts a wide range of [configuration options](options.md) and returns a set of [state props](state.md).
89
The way you use these differs slightly between the `useAsync` and `useFetch` hooks, and the `<Async>` component.

docs/guide/async-actions.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Async actions
2+
3+
Fetching data for display alone isn't sufficient for most applications. You'll often also want to submit data back to
4+
the server, or handle other types of asynchronous actions. To enable this, React Async has the concept of a
5+
[`deferFn`](api/options.md#deferfn).
6+
7+
Like `promiseFn`, a `deferFn` is a function that returns a Promise. The difference is that `deferFn` will not be
8+
automatically invoked by React Async when rendering the component. Instead it will have to be triggered by calling the
9+
[`run`](api/state.md#run) function provided by React Async.
10+
11+
```jsx
12+
import React, { useState } from "react"
13+
import { useAsync } from "react-async"
14+
15+
const subscribe = ([email], props, { signal }) =>
16+
fetch("/newsletter", { method: "POST", body: JSON.stringify({ email }), signal })
17+
18+
const NewsletterForm = () => {
19+
const { isPending, error, run } = useAsync({ deferFn: subscribe })
20+
const [email, setEmail] = useState("")
21+
22+
const handleSubmit = event => {
23+
event.preventDefault()
24+
run(email)
25+
}
26+
27+
return (
28+
<form onSubmit={handleSubmit}>
29+
<input type="email" value={email} onChange={event => setEmail(event.target.value)} />
30+
<button type="submit" disabled={isPending}>
31+
Subscribe
32+
</button>
33+
{error && <p>{error.message}</p>}
34+
</form>
35+
)
36+
}
37+
```
38+
39+
As you can see, the `deferFn` is invoked with 3 arguments: `args`, `props` and the AbortController. `args` is an array
40+
representing the arguments that were passed to `run`. In this case we passed the `email`, so we can extract that from
41+
the `args` array at the first index using [array destructuring] and pass it along to our `fetch` request.
42+
43+
[array destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring
44+
45+
## Sending data with `useFetch`
46+
47+
The above example can be simplified when we rely on [`useFetch`](api/interfaces.md#usefetch-hook) instead of
48+
constructing the request manually.
49+
50+
```jsx
51+
import React, { useState } from "react"
52+
import { useFetch } from "react-async"
53+
54+
const NewsletterForm = () => {
55+
const { isPending, error, run } = useFetch("/newsletter", { method: "POST" })
56+
const [email, setEmail] = useState("")
57+
58+
const handleSubmit = event => {
59+
event.preventDefault()
60+
run({ body: JSON.stringify({ email }) })
61+
}
62+
63+
return (
64+
<form onSubmit={handleSubmit}>
65+
<input type="email" value={email} onChange={event => setEmail(event.target.value)} />
66+
<button type="submit" disabled={isPending}>
67+
Subscribe
68+
</button>
69+
{error && <p>{error.message}</p>}
70+
</form>
71+
)
72+
}
73+
```
74+
75+
The [`run`](api/state.md#run) function for `useFetch` is a little special because it allows you to override the
76+
request's resource and other params. This way you can pass in the body, add dynamic headers or override the URL.

docs/guide/async-components.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Async components
2+
3+
The most common use case for React Async is data fetching. In single-page applications it's very common to dynamically
4+
load some data from a backend. React Async makes it incredibly easy to set this up, without having to worry about the
5+
details.
6+
7+
The mental model of React Async is component-first. Rather than loading data high up in your application and passing it
8+
down to a component for display, you perform the data loading at the component level. Such a component is called an
9+
async component. An async component can render its state in a meaningful way like any other component, or be logic-only.
10+
In that case it doesn't render any UI but instead passes its state down to its children. Such separation of concerns is
11+
good practice.
12+
13+
## Creating an async component with `useFetch`
14+
15+
The easiest way to create an async component for data fetching is through the
16+
[`useFetch` hook](api/interfaces.md#usefetch-hook):
17+
18+
```jsx
19+
import React from "react"
20+
import { useFetch } from "react-async"
21+
22+
const Person = ({ id }) => {
23+
const { data, error } = useFetch(`https://swapi.co/api/people/${id}/`, {
24+
headers: { accept: "application/json" },
25+
})
26+
if (error) return error.message
27+
if (data) return `Hi, my name is ${data.name}!`
28+
return null
29+
}
30+
31+
const App = () => {
32+
return <Person id={1} />
33+
}
34+
```
35+
36+
## More flexibility with `useAsync`
37+
38+
For most data fetching needs, `useFetch` is sufficient. However, sometimes you may want to take full control, for
39+
example if you want to combine multiple requests. In this case you can use the
40+
[`useAsync` hook](api/interfaces.md#useasync-hook).
41+
42+
The core concept of `useAsync` (and React Async in general), is the [`promiseFn`](api/options.md#promisefn): a function
43+
that returns a `Promise`. It's the fundamental concept for modelling asynchronous operations. It enables React Async to
44+
take control over scheduling, the Promise lifecycle and things like (re)starting an operation on user action or other
45+
changes. We've deliberately chosen the `Promise` as our primitive, because it's natively supported and has various
46+
utility methods like `Promise.all`. That's also why you'll find our terminology closely follows the Promise [states and
47+
fates].
48+
49+
The above example, written with `useAsync`, would look like this:
50+
51+
```jsx
52+
import React from "react"
53+
import { useAsync } from "react-async"
54+
55+
const fetchPerson = async ({ id }, { signal }) => {
56+
const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
57+
if (!response.ok) throw new Error(response.status)
58+
return response.json()
59+
}
60+
61+
const Person = ({ id }) => {
62+
const { data, error } = useAsync({ promiseFn: fetchPerson, id })
63+
if (error) return error.message
64+
if (data) return `Hi, my name is ${data.name}!`
65+
return null
66+
}
67+
68+
const App = () => {
69+
return <Person id={1} />
70+
}
71+
```
72+
73+
Notice the incoming parameters to `fetchPerson`. The `promiseFn` will be invoked with a `props` object and an
74+
`AbortController`. `props` are the options you passed to `useAsync`, which is why you can access the `id` property
75+
using [object destructuring]. The `AbortController` is created by React Async to enable [abortable fetch], so the
76+
underlying request will be aborted when the promise is cancelled (e.g. when a new one starts or we leave the page). We
77+
have to pass its `AbortSignal` down to `fetch` in order to wire this up.
78+
79+
[states and fates]: https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md
80+
[object destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring
81+
[abortable fetch]: https://developers.google.com/web/updates/2017/09/abortable-fetch

docs/guide/optimistic-updates.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Optimistic updates
2+
3+
A powerful pattern to improve your app's perceived performance is optimistic updates. When building an async action, you
4+
might be able to predict the outcome of the operation. If so, you can implement optimistic updates by proactively
5+
setting the `data` to the predicted value, when starting the async action. Once the action completes, it will update
6+
`data` to the actual value, probably the same value as predicted.
7+
8+
The following example uses both `promiseFn` and `deferFn` along with [`setData`](api/state.md#setdata) to implement
9+
optimistic updates.
10+
11+
```jsx
12+
import Async from "react-async"
13+
14+
const getAttendance = () => fetch("/attendance").then(() => true, () => false)
15+
const updateAttendance = ([attend]) =>
16+
fetch("/attendance", { method: attend ? "POST" : "DELETE" }).then(() => attend, () => !attend)
17+
18+
const AttendanceToggle = () => (
19+
<Async promiseFn={getAttendance} deferFn={updateAttendance}>
20+
{({ isPending, data: isAttending, run, setData }) => (
21+
<Toggle
22+
on={isAttending}
23+
onClick={() => {
24+
setData(!isAttending)
25+
run(!isAttending)
26+
}}
27+
disabled={isPending}
28+
/>
29+
)}
30+
</Async>
31+
)
32+
```
33+
34+
Here we have a switch to toggle attentance for an event. Clicking the toggle will most likely succeed, so we can predict
35+
the value it will have after completion (because we're just flipping a boolean).
36+
37+
Notice that React Async accepts both a `promiseFn` and a `deferFn` at the same time. This allows you to combine data
38+
fetching with performing actions. A typical example of where this is useful is with forms, where you first want to
39+
populate the fields with current values from the database, and send the new values back when submitting the form. Do
40+
note that `promiseFn` and `deferFn` operate on the same `data`, so they should both resolve to a similar kind of value.

docs/guide/separating-view-logic.md

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Separating view and logic
2+
3+
It's generally good practice to separate view components from logic components. Async components should preferably be
4+
logic-only. That means they don't render anything by themselves. Instead you can use the [render props] pattern to pass
5+
down the async state:
6+
7+
```jsx
8+
import React from "react"
9+
import { useAsync } from "react-async"
10+
11+
const fetchPerson = async ({ id }, { signal }) => {
12+
const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
13+
if (!response.ok) throw new Error(response.statusText)
14+
return response.json()
15+
}
16+
17+
const Person = ({ id }) => {
18+
const { data, error } = useAsync({ promiseFn: fetchPerson, id })
19+
return children(state)
20+
}
21+
22+
const App = () => {
23+
return (
24+
<Person id={1}>
25+
{({ isPending, data, error }) => {
26+
if (isPending) return "Loading..."
27+
if (error) return <ErrorMessage {...error} />
28+
if (data) return <Greeting {...data} />
29+
return null
30+
}}
31+
</Person>
32+
)
33+
}
34+
```
35+
36+
> `ErrorMessage` and `Greeting` would be separate view components defined elsewhere.
37+
38+
[render props]: https://reactjs.org/docs/render-props.html
39+
40+
## Cleaning up the JSX
41+
42+
You'll notice the render props pattern is very powerful, but can also lead to code that's hard to read and understand.
43+
To make your JSX more declarative and less cluttered, you can use the [`<Async>`](api/interfaces.md#async-component)
44+
component and its [state helpers](api/helpers.md). These take away the need for `if/else` statements and `return`
45+
keywords in your JSX.
46+
47+
```jsx
48+
import React from "react"
49+
import Async from "react-async"
50+
51+
const fetchPerson = async ({ id }, { signal }) => {
52+
const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
53+
if (!response.ok) throw new Error(response.statusText)
54+
return response.json()
55+
}
56+
57+
const App = () => {
58+
return (
59+
<Async promiseFn={fetchPerson} id={1}>
60+
<Async.Pending>Loading...</Async.Pending>
61+
<Async.Rejected>{error => <ErrorMessage {...error} />}</Async.Rejected>
62+
<Async.Fulfilled>{data => <Greeting {...data} />}</Async.Fulfilled>
63+
</Async>
64+
)
65+
}
66+
```
67+
68+
You should know that these helper components do not have to be direct children of the `<Async>` component. Because they
69+
are automatically wired up using [Context], they can be placed anywhere down the component tree, so long as they are
70+
descendants. You can also use helpers of the same type, multiple times.
71+
72+
Stand-alone versions of `<Async.Pending>` and the like are also available. However, these must be wired up manually by
73+
passing the `state` prop and are therefore only really useful when combined with one of the async hooks.
74+
75+
[context]: https://reactjs.org/docs/context.html

docs/guide/server-side-rendering.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Server-side rendering
2+
3+
There's a good chance you're using React with Server-side rendering (SSR), as many applications require this to be
4+
successful. If you happen to be using Next.js, it's really easy to integrate React Async. The crux is in setting a
5+
[`initialValue`](api/options.md#initialvalue), which is fetched server-side for initial page loads and passed along
6+
through rehydration.
7+
8+
```jsx
9+
import fetch from "isomorphic-unfetch"
10+
11+
const fetchPerson = async ({ id }) => {
12+
const response = await fetch(`https://swapi.co/api/people/${id}/`)
13+
if (!response.ok) throw new Error(response.status)
14+
return response.json()
15+
}
16+
17+
const Person = ({ id, person }) => (
18+
<Async promiseFn={fetchPerson} initialValue={person} id={id}>
19+
<Async.Pending>Loading...</Async.Pending>
20+
<Async.Rejected>{error => <ErrorMessage {...error} />}</Async.Rejected>
21+
<Async.Fulfilled>{data => <Greeting {...data} />}</Async.Fulfilled>
22+
</Async>
23+
)
24+
25+
Person.getInitialProps = async ({ req }) => {
26+
const id = req.params.id
27+
const person = await fetchPerson({ id })
28+
return { id, person }
29+
}
30+
```
31+
32+
If React Async is provided an `initialValue`, it will not invoke the `promiseFn` on mount. Instead it will use the
33+
`initialValue` to immediately set `data` or `error`, and render accordingly.

0 commit comments

Comments
 (0)