|
2 | 2 |
|
3 | 3 | Async component that waits on promises to resolve! |
4 | 4 |
|
5 | | -### Motivation |
| 5 | +## Motivation |
6 | 6 |
|
7 | 7 | Creating a promise is a synchronous action. If you hold a reference to a promise, you can eventually get the future value that it resolves to. Calling `setState` asynchronously in a component can cause a lot of headaches because of race conditions. The promise could still be in a pending state when the component unmounts or the props change. The `Async` component allows you to never worry about these race conditions and enables you to write your asynchronous react code as if it was synchronous. |
8 | 8 |
|
| 9 | +## Install |
| 10 | + |
| 11 | +``` |
| 12 | +yarn add react-async-await react |
| 13 | +``` |
| 14 | + |
| 15 | +## Async Component |
| 16 | + |
9 | 17 | The `Async` component |
10 | 18 |
|
11 | 19 | * throws uncaught promise rejections so they can be handled by an [error boundary](https://reactjs.org/docs/error-boundaries.html) |
12 | 20 | * injects render callback with resolved value |
13 | 21 | * renders synchronously if the promise was already resolved by the Async component |
14 | 22 | * prevents race conditions when props change and components unmount |
15 | 23 |
|
16 | | -### Install |
| 24 | +```js |
| 25 | +import React from "react"; |
| 26 | +import ReactDOM from "react-dom"; |
| 27 | +import { Async } from "react-async-await"; |
| 28 | + |
| 29 | +const getUser = id => |
| 30 | + fetch(`/api/users/${id}`).then(response => response.json()); |
| 31 | + |
| 32 | +class User extends React.Component { |
| 33 | + componentWillMount() { |
| 34 | + this.setState({ |
| 35 | + promise: undefined, |
| 36 | + error: undefined |
| 37 | + }); |
| 38 | + } |
| 39 | + |
| 40 | + componentDidMount() { |
| 41 | + this.setState({ |
| 42 | + promise: getUser(this.props.id) |
| 43 | + }); |
| 44 | + } |
| 45 | + |
| 46 | + componentWillReceiveProps(nextProps) { |
| 47 | + if (this.props.id !== nextProps.id) { |
| 48 | + this.setState({ |
| 49 | + promise: getUser(nextProps.id), |
| 50 | + error: undefined |
| 51 | + }); |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + // If this.state.promise rejects then Async component will throw error. |
| 56 | + // Error Boundaries allow you to recover from thrown errors. |
| 57 | + // @see https://reactjs.org/docs/error-boundaries.html |
| 58 | + componentDidCatch(error) { |
| 59 | + this.setState({ error }); |
| 60 | + } |
| 61 | + |
| 62 | + render() { |
| 63 | + if (this.state.error) { |
| 64 | + return <div>An error occurred!</div>; |
| 65 | + } |
| 66 | + |
| 67 | + return ( |
| 68 | + <Async await={this.state.promise}> |
| 69 | + {user => (user ? <div>Hello {user.name}!</div> : <div>Loading...</div>)} |
| 70 | + </Async> |
| 71 | + ); |
| 72 | + } |
| 73 | +} |
17 | 74 |
|
| 75 | +ReactDOM.render(<User id={1} />, document.getElementById("root")); |
18 | 76 | ``` |
19 | | -yarn add react-async-await react |
| 77 | + |
| 78 | +## createLoader Factory |
| 79 | + |
| 80 | +Create a wrapper component around `Async` that maps props to a promise when the component mounts. |
| 81 | + |
| 82 | +```js |
| 83 | +import React from "react"; |
| 84 | +import ReactDOM from "react-dom"; |
| 85 | +import { createLoader } from "react-async-await"; |
| 86 | + |
| 87 | +const getUser = id => fetch(`/api/users/${id}`).then(response => response.json()); |
| 88 | + |
| 89 | +const LoadUser = createLoader( |
| 90 | + props => getUser(props.id)), // loader |
| 91 | + props => props.id, // resolver |
| 92 | +); |
| 93 | + |
| 94 | +class User extends React.Component { |
| 95 | + componentWillMount() { |
| 96 | + this.setState({ |
| 97 | + error: undefined |
| 98 | + }); |
| 99 | + } |
| 100 | + |
| 101 | + componentWillReceiveProps(nextProps) { |
| 102 | + if (this.props.id !== nextProps.id) { |
| 103 | + this.setState({ |
| 104 | + error: undefined |
| 105 | + }); |
| 106 | + } |
| 107 | + } |
| 108 | + |
| 109 | + // If this.state.promise rejects then Async component will throw error. |
| 110 | + // Error Boundaries allow you to recover from thrown errors. |
| 111 | + // @see https://reactjs.org/docs/error-boundaries.html |
| 112 | + componentDidCatch(error) { |
| 113 | + this.setState({ error }); |
| 114 | + } |
| 115 | + |
| 116 | + render() { |
| 117 | + if (this.state.error) { |
| 118 | + return <div>An error occurred!</div>; |
| 119 | + } |
| 120 | + |
| 121 | + return ( |
| 122 | + <LoadUser id={this.props.id}> |
| 123 | + {user => (user ? <div>Hello {user.name}!</div> : <div>Loading...</div>)} |
| 124 | + </LoadUser> |
| 125 | + ); |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +ReactDOM.render(<User id={1} />, document.getElementById("root")); |
20 | 130 | ``` |
21 | 131 |
|
22 | | -### Basic Usage |
| 132 | +## Caching |
| 133 | + |
| 134 | +Coupling the `Async` component or `createLoader` factory with a promise cache can be extremely powerful. Instead of creating a new promise every time your component receives props, you can resolve to a cached promise by using techniques like memoization. Below is an example using [`lodash/memoize`](https://lodash.com/docs/4.17.5#memoize). |
23 | 135 |
|
24 | 136 | ```js |
25 | 137 | import React from "react"; |
26 | | -import { render } from "react-dom"; |
27 | | -import { Async } from "react-async-await"; |
| 138 | +import ReactDOM from "react-dom"; |
| 139 | +import { createLoader } from "react-async-await"; |
28 | 140 | import memoize from "lodash/memoize"; |
29 | 141 |
|
30 | 142 | // will return same promise when passed same id |
31 | 143 | // @see https://lodash.com/docs/4.17.5#memoize |
32 | | -const getUser = memoize(id => |
33 | | - fetch(`/api/users/${this.props.id}`).then(response => response.json()) |
| 144 | +const getUser = memoize( |
| 145 | + id => fetch(`/api/users/${id}`).then(response => response.json()), |
| 146 | + id => id, |
| 147 | +); |
| 148 | + |
| 149 | +const LoadUser = createLoader( |
| 150 | + props => getUser(props.id)), // loader |
| 151 | + props => props.id, // resolver |
34 | 152 | ); |
35 | 153 |
|
36 | 154 | class User extends React.Component { |
@@ -61,12 +179,12 @@ class User extends React.Component { |
61 | 179 | } |
62 | 180 |
|
63 | 181 | return ( |
64 | | - <Async await={getUser(this.props.id)}> |
| 182 | + <LoadUser id={this.props.id}> |
65 | 183 | {user => (user ? <div>Hello {user.name}!</div> : <div>Loading...</div>)} |
66 | | - </Async> |
| 184 | + </LoadUser> |
67 | 185 | ); |
68 | 186 | } |
69 | 187 | } |
70 | 188 |
|
71 | | -render(<User id={1} />, document.getElementById("root")); |
| 189 | +ReactDOM.render(<User id={1} />, document.getElementById("root")); |
72 | 190 | ``` |
0 commit comments