Usage
React Async offers three primary APIs: the useAsync
hook, the <Async>
component and the createInstance
factory function. Each has its unique benefits and downsides.
As a hook
The useAsync
hook (available from React v16.8.0) offers direct access to React Async's core functionality from within your own function components:
import { useAsync } from "react-async"
// You can use async/await or any function that returns a Promise
const loadPlayer = async ({ playerId }, { signal }) => {
const res = await fetch(`/api/players/${playerId}`, { signal })
if (!res.ok) throw new Error(res.statusText)
return res.json()
}
const MyComponent = () => {
const { data, error, isPending } = useAsync({ promiseFn: loadPlayer, playerId: 1 })
if (isPending) return "Loading..."
if (error) return `Something went wrong: ${error.message}`
if (data)
return (
<div>
<strong>Player data:</strong>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
return null
}
Using helper components can greatly improve readability of your render functions by not having to write all those conditional returns.
Or using the shorthand version:
const MyComponent = () => {
const { data, error, isPending } = useAsync(loadPlayer, options)
// ...
}
With useFetch
useFetch
Because fetch is so commonly used with useAsync
, there's a dedicated useFetch
hook for it:
import { useFetch } from "react-async"
const MyComponent = () => {
const headers = { Accept: "application/json" }
const { data, error, isPending, run } = useFetch("/api/example", { headers }, options)
// This will setup a promiseFn with a fetch request and JSON deserialization.
// you can later call `run` with an optional callback argument to
// last-minute modify the `init` parameter that is passed to `fetch`
function clickHandler() {
run(init => ({
...init,
headers: {
...init.headers,
authentication: "...",
},
}))
}
// alternatively, you can also just use an object that will be spread over `init`.
// please note that this is not deep-merged, so you might override properties present in the
// original `init` parameter
function clickHandler2() {
run({ body: JSON.stringify(formValues) })
}
}
useFetch
takes the same arguments as fetch itself, as well as options
to the underlying useAsync
hook. The options
object takes two special boolean properties: defer
and json
. These can be used to switch between deferFn
and promiseFn
, and enable JSON parsing. By default useFetch
automatically uses promiseFn
or deferFn
based on the request method (deferFn
for POST / PUT / PATCH / DELETE) and handles JSON parsing if the Accept
header is set to "application/json"
.
As a component
The classic interface to React Async. Simply use <Async>
directly in your JSX component tree, leveraging the render props pattern:
import Async from "react-async"
// Your promiseFn receives all props from Async and an AbortController instance
const loadPlayer = async ({ playerId }, { signal }) => {
const res = await fetch(`/api/players/${playerId}`, { signal })
if (!res.ok) throw new Error(res.statusText)
return res.json()
}
const MyComponent = () => (
<Async promiseFn={loadPlayer} playerId={1}>
{({ data, error, isPending }) => {
if (isPending) return "Loading..."
if (error) return `Something went wrong: ${error.message}`
if (data)
return (
<div>
<strong>Player data:</strong>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
return null
}}
</Async>
)
Using helper components can greatly improve readability of your render functions by not having to write all those conditional returns.
As a factory
You can also create your own component instances, allowing you to preconfigure them with options such as default onResolve
and onReject
callbacks.
import { createInstance } from "react-async"
const loadPlayer = async ({ playerId }, { signal }) => {
const res = await fetch(`/api/players/${playerId}`, { signal })
if (!res.ok) throw new Error(res.statusText)
return res.json()
}
// createInstance takes a defaultOptions object and a displayName (both optional)
const AsyncPlayer = createInstance({ promiseFn: loadPlayer }, "AsyncPlayer")
const MyComponent = () => (
<AsyncPlayer playerId={1}>
<AsyncPlayer.Fulfilled>{player => `Hello ${player.name}`}</AsyncPlayer.Fulfilled>
</AsyncPlayer>
)
With helper components
Several helper components are available to improve legibility. They can be used with useAsync
by passing in the state, or with <Async>
by using Context. Each of these components simply enables or disables rendering of its children based on the current state.
import { useAsync, IfPending, IfFulfilled, IfRejected } from "react-async"
const loadPlayer = async ({ playerId }, { signal }) => {
// ...
}
const MyComponent = () => {
const state = useAsync({ promiseFn: loadPlayer, playerId: 1 })
return (
<>
<IfPending state={state}>Loading...</IfPending>
<IfRejected state={state}>{error => `Something went wrong: ${error.message}`}</IfRejected>
<IfFulfilled state={state}>
{data => (
<div>
<strong>Player data:</strong>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</IfFulfilled>
</>
)
}
As compounds to <Async>
<Async>
Each of the helper components are also available as static properties of <Async>
. In this case you won't have to pass the state object, instead it will be automatically provided through Context.
import Async from "react-async"
const loadPlayer = async ({ playerId }, { signal }) => {
const res = await fetch(`/api/players/${playerId}`, { signal })
if (!res.ok) throw new Error(res.statusText)
return res.json()
}
const MyComponent = () => (
<Async promiseFn={loadPlayer} playerId={1}>
<Async.Pending>Loading...</Async.Pending>
<Async.Fulfilled>
{data => (
<div>
<strong>Player data:</strong>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)}
</Async.Fulfilled>
<Async.Rejected>{error => `Something went wrong: ${error.message}`}</Async.Rejected>
</Async>
)
Last updated
Was this helpful?