|
| 1 | +# Usage |
| 2 | + |
| 3 | +React Async offers three primary APIs: the `useAsync` hook, the `<Async>` component and the `createInstance` |
| 4 | +factory function. Each has its unique benefits and downsides. |
| 5 | + |
| 6 | +## As a hook |
| 7 | + |
| 8 | +The `useAsync` hook (available [from React v16.8.0](https://reactjs.org/hooks)) offers direct access to React Async's |
| 9 | +core functionality from within your own function components: |
| 10 | + |
| 11 | +```jsx |
| 12 | +import { useAsync } from "react-async" |
| 13 | + |
| 14 | +const loadCustomer = async ({ customerId }, { signal }) => { |
| 15 | + const res = await fetch(`/api/customers/${customerId}`, { signal }) |
| 16 | + if (!res.ok) throw new Error(res) |
| 17 | + return res.json() |
| 18 | +} |
| 19 | + |
| 20 | +const MyComponent = () => { |
| 21 | + const { data, error, isPending } = useAsync({ promiseFn: loadCustomer, customerId: 1 }) |
| 22 | + if (isPending) return "Loading..." |
| 23 | + if (error) return `Something went wrong: ${error.message}` |
| 24 | + if (data) |
| 25 | + return ( |
| 26 | + <div> |
| 27 | + <strong>Loaded some data:</strong> |
| 28 | + <pre>{JSON.stringify(data, null, 2)}</pre> |
| 29 | + </div> |
| 30 | + ) |
| 31 | + return null |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +> Using [helper components](#with-helper-components) can greatly improve readability of your render functions by not |
| 36 | +> having to write all those conditional returns. |
| 37 | +
|
| 38 | +Or using the shorthand version: |
| 39 | + |
| 40 | +```jsx |
| 41 | +const MyComponent = () => { |
| 42 | + const { data, error, isPending } = useAsync(loadCustomer, options) |
| 43 | + // ... |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +### With `useFetch` |
| 48 | + |
| 49 | +Because fetch is so commonly used with `useAsync`, there's a dedicated `useFetch` hook for it: |
| 50 | + |
| 51 | +```jsx |
| 52 | +import { useFetch } from "react-async" |
| 53 | + |
| 54 | +const MyComponent = () => { |
| 55 | + const headers = { Accept: "application/json" } |
| 56 | + const { data, error, isPending, run } = useFetch("/api/example", { headers }, options) |
| 57 | + // This will setup a promiseFn with a fetch request and JSON deserialization. |
| 58 | + |
| 59 | + // you can later call `run` with an optional callback argument to |
| 60 | + // last-minute modify the `init` parameter that is passed to `fetch` |
| 61 | + function clickHandler() { |
| 62 | + run(init => ({ |
| 63 | + ...init, |
| 64 | + headers: { |
| 65 | + ...init.headers, |
| 66 | + authentication: "...", |
| 67 | + }, |
| 68 | + })) |
| 69 | + } |
| 70 | + |
| 71 | + // alternatively, you can also just use an object that will be spread over `init`. |
| 72 | + // please note that this is not deep-merged, so you might override properties present in the |
| 73 | + // original `init` parameter |
| 74 | + function clickHandler2() { |
| 75 | + run({ body: JSON.stringify(formValues) }) |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +`useFetch` takes the same arguments as [fetch] itself, as well as `options` to the underlying `useAsync` hook. The |
| 81 | +`options` object takes two special boolean properties: `defer` and `json`. These can be used to switch between |
| 82 | +`deferFn` and `promiseFn`, and enable JSON parsing. By default `useFetch` automatically uses `promiseFn` or `deferFn` |
| 83 | +based on the request method (`deferFn` for POST / PUT / PATCH / DELETE) and handles JSON parsing if the `Accept` header |
| 84 | +is set to `"application/json"`. |
| 85 | + |
| 86 | +[fetch]: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch |
| 87 | + |
| 88 | +## As a component |
| 89 | + |
| 90 | +The classic interface to React Async. Simply use `<Async>` directly in your JSX component tree, leveraging the render |
| 91 | +props pattern: |
| 92 | + |
| 93 | +```jsx |
| 94 | +import Async from "react-async" |
| 95 | + |
| 96 | +// Your promiseFn receives all props from Async and an AbortController instance |
| 97 | +const loadCustomer = ({ customerId }, { signal }) => |
| 98 | + fetch(`/api/customers/${customerId}`, { signal }) |
| 99 | + .then(res => (res.ok ? res : Promise.reject(res))) |
| 100 | + .then(res => res.json()) |
| 101 | + |
| 102 | +const MyComponent = () => ( |
| 103 | + <Async promiseFn={loadCustomer} customerId={1}> |
| 104 | + {({ data, error, isPending }) => { |
| 105 | + if (isPending) return "Loading..." |
| 106 | + if (error) return `Something went wrong: ${error.message}` |
| 107 | + if (data) |
| 108 | + return ( |
| 109 | + <div> |
| 110 | + <strong>Loaded some data:</strong> |
| 111 | + <pre>{JSON.stringify(data, null, 2)}</pre> |
| 112 | + </div> |
| 113 | + ) |
| 114 | + return null |
| 115 | + }} |
| 116 | + </Async> |
| 117 | +) |
| 118 | +``` |
| 119 | + |
| 120 | +> Using [helper components](#with-helper-components) can greatly improve readability of your render functions by not |
| 121 | +> having to write all those conditional returns. |
| 122 | +
|
| 123 | +## As a factory |
| 124 | + |
| 125 | +You can also create your own component instances, allowing you to preconfigure them with options such as default |
| 126 | +`onResolve` and `onReject` callbacks. |
| 127 | + |
| 128 | +```jsx |
| 129 | +import { createInstance } from "react-async" |
| 130 | + |
| 131 | +const loadCustomer = ({ customerId }, { signal }) => |
| 132 | + fetch(`/api/customers/${customerId}`, { signal }) |
| 133 | + .then(res => (res.ok ? res : Promise.reject(res))) |
| 134 | + .then(res => res.json()) |
| 135 | + |
| 136 | +// createInstance takes a defaultProps object and a displayName (both optional) |
| 137 | +const AsyncCustomer = createInstance({ promiseFn: loadCustomer }, "AsyncCustomer") |
| 138 | + |
| 139 | +const MyComponent = () => ( |
| 140 | + <AsyncCustomer customerId={1}> |
| 141 | + <AsyncCustomer.Fulfilled>{customer => `Hello ${customer.name}`}</AsyncCustomer.Fulfilled> |
| 142 | + </AsyncCustomer> |
| 143 | +) |
| 144 | +``` |
| 145 | + |
| 146 | +## With helper components |
| 147 | + |
| 148 | +Several [helper components](#helper-components) are available to improve legibility. They can be used with `useAsync` |
| 149 | +by passing in the state, or with `<Async>` by using Context. Each of these components simply enables or disables |
| 150 | +rendering of its children based on the current state. |
| 151 | + |
| 152 | +```jsx |
| 153 | +import { useAsync, IfPending, IfFulfilled, IfRejected } from "react-async" |
| 154 | + |
| 155 | +const loadCustomer = async ({ customerId }, { signal }) => { |
| 156 | + // ... |
| 157 | +} |
| 158 | + |
| 159 | +const MyComponent = () => { |
| 160 | + const state = useAsync({ promiseFn: loadCustomer, customerId: 1 }) |
| 161 | + return ( |
| 162 | + <> |
| 163 | + <IfPending state={state}>Loading...</IfPending> |
| 164 | + <IfRejected state={state}>{error => `Something went wrong: ${error.message}`}</IfRejected> |
| 165 | + <IfFulfilled state={state}> |
| 166 | + {data => ( |
| 167 | + <div> |
| 168 | + <strong>Loaded some data:</strong> |
| 169 | + <pre>{JSON.stringify(data, null, 2)}</pre> |
| 170 | + </div> |
| 171 | + )} |
| 172 | + </IfFulfilled> |
| 173 | + </> |
| 174 | + ) |
| 175 | +} |
| 176 | +``` |
| 177 | + |
| 178 | +### As compounds to `<Async>` |
| 179 | + |
| 180 | +Each of the helper components are also available as static properties of `<Async>`. In this case you won't have to pass |
| 181 | +the state object, instead it will be automatically provided through Context. |
| 182 | + |
| 183 | +```jsx |
| 184 | +import Async from "react-async" |
| 185 | + |
| 186 | +const loadCustomer = ({ customerId }, { signal }) => |
| 187 | + fetch(`/api/customers/${customerId}`, { signal }) |
| 188 | + .then(res => (res.ok ? res : Promise.reject(res))) |
| 189 | + .then(res => res.json()) |
| 190 | + |
| 191 | +const MyComponent = () => ( |
| 192 | + <Async promiseFn={loadCustomer} customerId={1}> |
| 193 | + <Async.Pending>Loading...</Async.Pending> |
| 194 | + <Async.Fulfilled> |
| 195 | + {data => ( |
| 196 | + <div> |
| 197 | + <strong>Loaded some data:</strong> |
| 198 | + <pre>{JSON.stringify(data, null, 2)}</pre> |
| 199 | + </div> |
| 200 | + )} |
| 201 | + </Async.Fulfilled> |
| 202 | + <Async.Rejected>{error => `Something went wrong: ${error.message}`}</Async.Rejected> |
| 203 | + </Async> |
| 204 | +) |
| 205 | +``` |
0 commit comments