Skip to content

Commit 74a93a5

Browse files
authored
Standalone helper components (#41)
* Separate helper components from Async itself so they can be used with hooks. * Update README.
1 parent 14e2ff5 commit 74a93a5

File tree

6 files changed

+468
-173
lines changed

6 files changed

+468
-173
lines changed

README.md

Lines changed: 119 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ error states, without assumptions about the shape of your data or the type of re
6464
- [As a hook](#as-a-hook)
6565
- [With `useFetch`](#with-usefetch)
6666
- [As a component](#as-a-component)
67-
- [With helper components](#with-helper-components)
6867
- [As a factory](#as-a-factory)
68+
- [With helper components](#with-helper-components)
6969
- [API](#api)
7070
- [Options](#options)
7171
- [Render props](#render-props)
@@ -168,6 +168,9 @@ const MyComponent = () => {
168168
}
169169
```
170170

171+
> Using [helper components](#with-helper-components) can greatly improve readability of your render functions by not
172+
> having to write all those conditional returns.
173+
171174
Or using the shorthand version:
172175

173176
```jsx
@@ -231,56 +234,90 @@ const MyComponent = () => (
231234
)
232235
```
233236

234-
#### With helper components
237+
> Using [helper components](#with-helper-components) can greatly improve readability of your render functions by not
238+
> having to write all those conditional returns.
235239
236-
Several [helper components](#helper-components) are available for better legibility. These don't have to be direct
237-
children of `<Async>`, because they use Context, offering full flexibility. You can even use render props and helper
238-
components together.
240+
### As a factory
241+
242+
You can also create your own component instances, allowing you to preconfigure them with options such as default
243+
`onResolve` and `onReject` callbacks.
239244

240245
```jsx
241-
import Async from "react-async"
246+
import { createInstance } from "react-async"
242247

243248
const loadCustomer = ({ customerId }, { signal }) =>
244249
fetch(`/api/customers/${customerId}`, { signal })
245250
.then(res => (res.ok ? res : Promise.reject(res)))
246251
.then(res => res.json())
247252

253+
// createInstance takes a defaultProps object and a displayName (both optional)
254+
const AsyncCustomer = createInstance({ promiseFn: loadCustomer }, "AsyncCustomer")
255+
248256
const MyComponent = () => (
249-
<Async promiseFn={loadCustomer} customerId={1}>
250-
<Async.Loading>Loading...</Async.Loading>
251-
<Async.Fulfilled>
252-
{data => (
253-
<div>
254-
<strong>Loaded some data:</strong>
255-
<pre>{JSON.stringify(data, null, 2)}</pre>
256-
</div>
257-
)}
258-
</Async.Fulfilled>
259-
<Async.Rejected>{error => `Something went wrong: ${error.message}`}</Async.Rejected>
260-
</Async>
257+
<AsyncCustomer customerId={1}>
258+
<AsyncCustomer.Fulfilled>{customer => `Hello ${customer.name}`}</AsyncCustomer.Fulfilled>
259+
</AsyncCustomer>
261260
)
262261
```
263262

264-
### As a factory
263+
### With helper components
265264

266-
You can also create your own component instances, allowing you to preconfigure them with options such as default
267-
`onResolve` and `onReject` callbacks.
265+
Several [helper components](#helper-components) are available to improve legibility. They can be used with `useAsync`
266+
by passing in the state, or with `<Async>` by using Context. Each of these components simply enables or disables
267+
rendering of its children based on the current state.
268268

269269
```jsx
270-
import { createInstance } from "react-async"
270+
import { useAsync, Pending, Fulfilled, Rejected } from "react-async"
271+
272+
const loadCustomer = async ({ customerId }, { signal }) => {
273+
// ...
274+
}
275+
276+
const MyComponent = () => {
277+
const state = useAsync({ promiseFn: loadCustomer, customerId: 1 })
278+
return (
279+
<>
280+
<Pending state={state}>Loading...</Pending>
281+
<Rejected state={state}>{error => `Something went wrong: ${error.message}`}</Rejected>
282+
<Fulfilled state={state}>
283+
{data => (
284+
<div>
285+
<strong>Loaded some data:</strong>
286+
<pre>{JSON.stringify(data, null, 2)}</pre>
287+
</div>
288+
)}
289+
</Fulfilled>
290+
</>
291+
)
292+
}
293+
```
294+
295+
#### As compounds to <Async>
296+
297+
Each of the helper components are also available as static properties of `<Async>`. In this case you won't have to pass
298+
the state object, instead it will be automatically provided through Context.
299+
300+
```jsx
301+
import Async from "react-async"
271302

272303
const loadCustomer = ({ customerId }, { signal }) =>
273304
fetch(`/api/customers/${customerId}`, { signal })
274305
.then(res => (res.ok ? res : Promise.reject(res)))
275306
.then(res => res.json())
276307

277-
// createInstance takes a defaultProps object and a displayName (both optional)
278-
const AsyncCustomer = createInstance({ promiseFn: loadCustomer }, "AsyncCustomer")
279-
280308
const MyComponent = () => (
281-
<AsyncCustomer customerId={1}>
282-
<AsyncCustomer.Fulfilled>{customer => `Hello ${customer.name}`}</AsyncCustomer.Fulfilled>
283-
</AsyncCustomer>
309+
<Async promiseFn={loadCustomer} customerId={1}>
310+
<Async.Loading>Loading...</Async.Loading>
311+
<Async.Fulfilled>
312+
{data => (
313+
<div>
314+
<strong>Loaded some data:</strong>
315+
<pre>{JSON.stringify(data, null, 2)}</pre>
316+
</div>
317+
)}
318+
</Async.Fulfilled>
319+
<Async.Rejected>{error => `Something went wrong: ${error.message}`}</Async.Rejected>
320+
</Async>
284321
)
285322
```
286323

@@ -554,17 +591,27 @@ invoked after the state update is completed. Returns the error to enable chainin
554591
React Async provides several helper components that make your JSX more declarative and less cluttered.
555592
They don't have to be direct children of `<Async>` and you can use the same component several times.
556593

557-
### `<Async.Initial>`
594+
### `<Initial>` / `<Async.Initial>`
558595

559596
Renders only while the deferred promise is still waiting to be run, or you have not provided any promise.
560597

561598
#### Props
562599

563-
- `persist` `boolean` Show until we have data, even while loading or when an error occurred. By default it hides as soon as the promise starts loading.
564600
- `children` `function(state: Object): Node | Node` Render function or React Node.
601+
- `state` `object` Async state object (return value of `useAsync()`).
602+
- `persist` `boolean` Show until we have data, even while loading or when an error occurred. By default it hides as soon as the promise starts loading.
565603

566604
#### Examples
567605

606+
```jsx
607+
const state = useAsync(...)
608+
return (
609+
<Initial state={state}>
610+
<p>This text is only rendered while `run` has not yet been invoked on `deferFn`.</p>
611+
</Initial>
612+
)
613+
```
614+
568615
```jsx
569616
<Async deferFn={deferFn}>
570617
<Async.Initial>
@@ -587,19 +634,29 @@ Renders only while the deferred promise is still waiting to be run, or you have
587634
</Async.Initial>
588635
```
589636

590-
### `<Async.Pending>`
637+
### `<Pending>` / `<Async.Pending>`
591638

592639
This component renders only while the promise is pending (aka loading) (unsettled).
593640

594641
Alias: `<Async.Loading>`
595642

596643
#### Props
597644

598-
- `initial` `boolean` Show only on initial load (when `data` is `undefined`).
599645
- `children` `function(state: Object): Node | Node` Render function or React Node.
646+
- `state` `object` Async state object (return value of `useAsync()`).
647+
- `initial` `boolean` Show only on initial load (when `data` is `undefined`).
600648

601649
#### Examples
602650

651+
```jsx
652+
const state = useAsync(...)
653+
return (
654+
<Pending state={state}>
655+
<p>This text is only rendered while performing the initial load.</p>
656+
</Pending>
657+
)
658+
```
659+
603660
```jsx
604661
<Async.Pending initial>
605662
<p>This text is only rendered while performing the initial load.</p>
@@ -610,19 +667,29 @@ Alias: `<Async.Loading>`
610667
<Async.Pending>{({ startedAt }) => `Loading since ${startedAt.toISOString()}`}</Async.Pending>
611668
```
612669

613-
### `<Async.Fulfilled>`
670+
### `<Fulfilled>` / `<Async.Fulfilled>`
614671

615672
This component renders only when the promise is fulfilled with data (`data !== undefined`).
616673

617674
Alias: `<Async.Resolved>`
618675

619676
#### Props
620677

621-
- `persist` `boolean` Show old data while loading new data. By default it hides as soon as a new promise starts.
622678
- `children` `function(data: any, state: Object): Node | Node` Render function or React Node.
679+
- `state` `object` Async state object (return value of `useAsync()`).
680+
- `persist` `boolean` Show old data while loading new data. By default it hides as soon as a new promise starts.
623681

624682
#### Examples
625683

684+
```jsx
685+
const state = useAsync(...)
686+
return (
687+
<Fulfilled state={state}>
688+
{data => <pre>{JSON.stringify(data)}</pre>}
689+
</Fulfilled>
690+
)
691+
```
692+
626693
```jsx
627694
<Async.Fulfilled persist>{data => <pre>{JSON.stringify(data)}</pre>}</Async.Fulfilled>
628695
```
@@ -633,17 +700,23 @@ Alias: `<Async.Resolved>`
633700
</Async.Fulfilled>
634701
```
635702

636-
### `<Async.Rejected>`
703+
### `<Rejected>` / `<Async.Rejected>`
637704

638705
This component renders only when the promise is rejected.
639706

640707
#### Props
641708

642-
- `persist` `boolean` Show old error while loading new data. By default it hides as soon as a new promise starts.
643709
- `children` `function(error: Error, state: Object): Node | Node` Render function or React Node.
710+
- `state` `object` Async state object (return value of `useAsync()`).
711+
- `persist` `boolean` Show old error while loading new data. By default it hides as soon as a new promise starts.
644712

645713
#### Examples
646714

715+
```jsx
716+
const state = useAsync(...)
717+
return <Rejected state={state}>Oops.</Rejected>
718+
```
719+
647720
```jsx
648721
<Async.Rejected persist>Oops.</Async.Rejected>
649722
```
@@ -652,14 +725,22 @@ This component renders only when the promise is rejected.
652725
<Async.Rejected>{error => `Unexpected error: ${error.message}`}</Async.Rejected>
653726
```
654727

655-
### `<Async.Settled>`
728+
### `<Settled>` / `<Async.Settled>`
656729

657730
This component renders only when the promise is fulfilled or rejected.
658731

659732
#### Props
660733

661-
- `persist` `boolean` Show old data or error while loading new data. By default it hides as soon as a new promise starts.
662734
- `children` `function(state: Object): Node | Node` Render function or React Node.
735+
- `state` `object` Async state object (return value of `useAsync()`).
736+
- `persist` `boolean` Show old data or error while loading new data. By default it hides as soon as a new promise starts.
737+
738+
#### Examples
739+
740+
```jsx
741+
const state = useAsync(...)
742+
return <Settled state={state}>{state => `Finished at ${state.finishedAt.toISOString()}`</Settled>
743+
```
663744
664745
## Usage examples
665746

0 commit comments

Comments
 (0)