Skip to content
Permalink
Browse files

Add guide to docs.

  • Loading branch information...
ghengeveld committed Oct 8, 2019
1 parent a5bb323 commit 4c60795d0a7d040e89e4d36129d4ee306e203e46
@@ -112,6 +112,14 @@ Use it with `fetch`, Axios or other data fetching libraries, even GraphQL.
- [State properties](https://docs.react-async.com/api/state)
- [Helper components](https://docs.react-async.com/api/helpers)

## Guide

- [Async components](https://docs.react-async.com/guide/async-components)
- [Separating view and logic](https://docs.react-async.com/guide/separating-view-logic)
- [Async actions](https://docs.react-async.com/guide/async-actions)
- [Optimistic updates](https://docs.react-async.com/guide/optimistic-updates)
- [Server-side rendering](https://docs.react-async.com/guide/server-side-rendering)

# Contributors

Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -15,3 +15,11 @@
- [Configuration options](api/options.md)
- [State properties](api/state.md)
- [Helper components](api/helpers.md)

## Guide

- [Async components](guide/async-components.md)
- [Separating view and logic](guide/separating-view-logic.md)
- [Async actions](guide/async-actions.md)
- [Optimistic updates](guide/optimistic-updates.md)
- [Server-side rendering](guide/server-side-rendering.md)
@@ -2,7 +2,8 @@

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

React Async accepts a wide range of [configuration options](options.md) and returns a set of [state props](state.md).
The way you use these differs slightly between the `useAsync` and `useFetch` hooks, and the `<Async>` component.
@@ -0,0 +1,76 @@
# Async actions

Fetching data for display alone isn't sufficient for most applications. You'll often also want to submit data back to
the server, or handle other types of asynchronous actions. To enable this, React Async has the concept of a
[`deferFn`](api/options.md#deferfn).

Like `promiseFn`, a `deferFn` is a function that returns a Promise. The difference is that `deferFn` will not be
automatically invoked by React Async when rendering the component. Instead it will have to be triggered by calling the
[`run`](api/state.md#run) function provided by React Async.

```jsx
import React, { useState } from "react"
import { useAsync } from "react-async"
const subscribe = ([email], props, { signal }) =>
fetch("/newsletter", { method: "POST", body: JSON.stringify({ email }), signal })
const NewsletterForm = () => {
const { isPending, error, run } = useAsync({ deferFn: subscribe })
const [email, setEmail] = useState("")
const handleSubmit = event => {
event.preventDefault()
run(email)
}
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={event => setEmail(event.target.value)} />
<button type="submit" disabled={isPending}>
Subscribe
</button>
{error && <p>{error.message}</p>}
</form>
)
}
```

As you can see, the `deferFn` is invoked with 3 arguments: `args`, `props` and the AbortController. `args` is an array
representing the arguments that were passed to `run`. In this case we passed the `email`, so we can extract that from
the `args` array at the first index using [array destructuring] and pass it along to our `fetch` request.

[array destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring

## Sending data with `useFetch`

The above example can be simplified when we rely on [`useFetch`](api/interfaces.md#usefetch-hook) instead of
constructing the request manually.

```jsx
import React, { useState } from "react"
import { useFetch } from "react-async"
const NewsletterForm = () => {
const { isPending, error, run } = useFetch("/newsletter", { method: "POST" })
const [email, setEmail] = useState("")
const handleSubmit = event => {
event.preventDefault()
run({ body: JSON.stringify({ email }) })
}
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={event => setEmail(event.target.value)} />
<button type="submit" disabled={isPending}>
Subscribe
</button>
{error && <p>{error.message}</p>}
</form>
)
}
```

The [`run`](api/state.md#run) function for `useFetch` is a little special because it allows you to override the
request's resource and other params. This way you can pass in the body, add dynamic headers or override the URL.
@@ -0,0 +1,81 @@
# Async components

The most common use case for React Async is data fetching. In single-page applications it's very common to dynamically
load some data from a backend. React Async makes it incredibly easy to set this up, without having to worry about the
details.

The mental model of React Async is component-first. Rather than loading data high up in your application and passing it
down to a component for display, you perform the data loading at the component level. Such a component is called an
async component. An async component can render its state in a meaningful way like any other component, or be logic-only.
In that case it doesn't render any UI but instead passes its state down to its children. Such separation of concerns is
good practice.

## Creating an async component with `useFetch`

The easiest way to create an async component for data fetching is through the
[`useFetch` hook](api/interfaces.md#usefetch-hook):

```jsx
import React from "react"
import { useFetch } from "react-async"
const Person = ({ id }) => {
const { data, error } = useFetch(`https://swapi.co/api/people/${id}/`, {
headers: { accept: "application/json" },
})
if (error) return error.message
if (data) return `Hi, my name is ${data.name}!`
return null
}
const App = () => {
return <Person id={1} />
}
```

## More flexibility with `useAsync`

For most data fetching needs, `useFetch` is sufficient. However, sometimes you may want to take full control, for
example if you want to combine multiple requests. In this case you can use the
[`useAsync` hook](api/interfaces.md#useasync-hook).

The core concept of `useAsync` (and React Async in general), is the [`promiseFn`](api/options.md#promisefn): a function
that returns a `Promise`. It's the fundamental concept for modelling asynchronous operations. It enables React Async to
take control over scheduling, the Promise lifecycle and things like (re)starting an operation on user action or other
changes. We've deliberately chosen the `Promise` as our primitive, because it's natively supported and has various
utility methods like `Promise.all`. That's also why you'll find our terminology closely follows the Promise [states and
fates].

The above example, written with `useAsync`, would look like this:

```jsx
import React from "react"
import { useAsync } from "react-async"
const fetchPerson = async ({ id }, { signal }) => {
const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
if (!response.ok) throw new Error(response.status)
return response.json()
}
const Person = ({ id }) => {
const { data, error } = useAsync({ promiseFn: fetchPerson, id })
if (error) return error.message
if (data) return `Hi, my name is ${data.name}!`
return null
}
const App = () => {
return <Person id={1} />
}
```

Notice the incoming parameters to `fetchPerson`. The `promiseFn` will be invoked with a `props` object and an
`AbortController`. `props` are the options you passed to `useAsync`, which is why you can access the `id` property
using [object destructuring]. The `AbortController` is created by React Async to enable [abortable fetch], so the
underlying request will be aborted when the promise is cancelled (e.g. when a new one starts or we leave the page). We
have to pass its `AbortSignal` down to `fetch` in order to wire this up.

[states and fates]: https://github.com/domenic/promises-unwrapping/blob/master/docs/states-and-fates.md
[object destructuring]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring
[abortable fetch]: https://developers.google.com/web/updates/2017/09/abortable-fetch
@@ -0,0 +1,40 @@
# Optimistic updates

A powerful pattern to improve your app's perceived performance is optimistic updates. When building an async action, you
might be able to predict the outcome of the operation. If so, you can implement optimistic updates by proactively
setting the `data` to the predicted value, when starting the async action. Once the action completes, it will update
`data` to the actual value, probably the same value as predicted.

The following example uses both `promiseFn` and `deferFn` along with [`setData`](api/state.md#setdata) to implement
optimistic updates.

```jsx
import Async from "react-async"
const getAttendance = () => fetch("/attendance").then(() => true, () => false)
const updateAttendance = ([attend]) =>
fetch("/attendance", { method: attend ? "POST" : "DELETE" }).then(() => attend, () => !attend)
const AttendanceToggle = () => (
<Async promiseFn={getAttendance} deferFn={updateAttendance}>
{({ isPending, data: isAttending, run, setData }) => (
<Toggle
on={isAttending}
onClick={() => {
setData(!isAttending)
run(!isAttending)
}}
disabled={isPending}
/>
)}
</Async>
)
```

Here we have a switch to toggle attentance for an event. Clicking the toggle will most likely succeed, so we can predict
the value it will have after completion (because we're just flipping a boolean).

Notice that React Async accepts both a `promiseFn` and a `deferFn` at the same time. This allows you to combine data
fetching with performing actions. A typical example of where this is useful is with forms, where you first want to
populate the fields with current values from the database, and send the new values back when submitting the form. Do
note that `promiseFn` and `deferFn` operate on the same `data`, so they should both resolve to a similar kind of value.
@@ -0,0 +1,75 @@
# Separating view and logic

It's generally good practice to separate view components from logic components. Async components should preferably be
logic-only. That means they don't render anything by themselves. Instead you can use the [render props] pattern to pass
down the async state:

```jsx
import React from "react"
import { useAsync } from "react-async"
const fetchPerson = async ({ id }, { signal }) => {
const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
if (!response.ok) throw new Error(response.statusText)
return response.json()
}
const Person = ({ id }) => {
const { data, error } = useAsync({ promiseFn: fetchPerson, id })
return children(state)
}
const App = () => {
return (
<Person id={1}>
{({ isPending, data, error }) => {
if (isPending) return "Loading..."
if (error) return <ErrorMessage {...error} />
if (data) return <Greeting {...data} />
return null
}}
</Person>
)
}
```

> `ErrorMessage` and `Greeting` would be separate view components defined elsewhere.
[render props]: https://reactjs.org/docs/render-props.html

## Cleaning up the JSX

You'll notice the render props pattern is very powerful, but can also lead to code that's hard to read and understand.
To make your JSX more declarative and less cluttered, you can use the [`<Async>`](api/interfaces.md#async-component)
component and its [state helpers](api/helpers.md). These take away the need for `if/else` statements and `return`
keywords in your JSX.

```jsx
import React from "react"
import Async from "react-async"
const fetchPerson = async ({ id }, { signal }) => {
const response = await fetch(`https://swapi.co/api/people/${id}/`, { signal })
if (!response.ok) throw new Error(response.statusText)
return response.json()
}
const App = () => {
return (
<Async promiseFn={fetchPerson} id={1}>
<Async.Pending>Loading...</Async.Pending>
<Async.Rejected>{error => <ErrorMessage {...error} />}</Async.Rejected>
<Async.Fulfilled>{data => <Greeting {...data} />}</Async.Fulfilled>
</Async>
)
}
```

You should know that these helper components do not have to be direct children of the `<Async>` component. Because they
are automatically wired up using [Context], they can be placed anywhere down the component tree, so long as they are
descendants. You can also use helpers of the same type, multiple times.

Stand-alone versions of `<Async.Pending>` and the like are also available. However, these must be wired up manually by
passing the `state` prop and are therefore only really useful when combined with one of the async hooks.

[context]: https://reactjs.org/docs/context.html
@@ -0,0 +1,33 @@
# Server-side rendering

There's a good chance you're using React with Server-side rendering (SSR), as many applications require this to be
successful. If you happen to be using Next.js, it's really easy to integrate React Async. The crux is in setting a
[`initialValue`](api/options.md#initialvalue), which is fetched server-side for initial page loads and passed along
through rehydration.

```jsx
import fetch from "isomorphic-unfetch"
const fetchPerson = async ({ id }) => {
const response = await fetch(`https://swapi.co/api/people/${id}/`)
if (!response.ok) throw new Error(response.status)
return response.json()
}
const Person = ({ id, person }) => (
<Async promiseFn={fetchPerson} initialValue={person} id={id}>
<Async.Pending>Loading...</Async.Pending>
<Async.Rejected>{error => <ErrorMessage {...error} />}</Async.Rejected>
<Async.Fulfilled>{data => <Greeting {...data} />}</Async.Fulfilled>
</Async>
)
Person.getInitialProps = async ({ req }) => {
const id = req.params.id
const person = await fetchPerson({ id })
return { id, person }
}
```

If React Async is provided an `initialValue`, it will not invoke the `promiseFn` on mount. Instead it will use the
`initialValue` to immediately set `data` or `error`, and render accordingly.

0 comments on commit 4c60795

Please sign in to comment.
You can’t perform that action at this time.