-
Notifications
You must be signed in to change notification settings - Fork 49.9k
Add a guide on loading data asynchronously #8098
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
b06e0d5
382fee4
b545300
34aedac
4c92f0e
55ead66
4b1fa64
461082c
763c790
c1a36a1
72632e9
a9a6007
74cc978
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| --- | ||
| id: loading-data-asynchronously | ||
| title: Loading Data Asynchronously | ||
| permalink: docs/loading-data-asynchronously.html | ||
| --- | ||
|
|
||
| Often, the data that a component needs is not available at initial render. We can load data asynchronously in the `componentDidMount` [lifecycle hook](/react/docs/react-component.html#componentdidmount). | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to explain the key concept somewhere early. We need to hammer home the idea that in React, any update means a change in the state. It doesn't have any special capabilities for handling AJAX: if you want to change the UI as a result of data arriving, you need to change the state. |
||
| In the following example we use the `fetch` [browser API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to retrieve information about Facebook's Gists on GitHub and store them in the state. | ||
|
|
||
| ```javascript{7-11} | ||
| class Gists extends React.Component { | ||
| constructor(props) { | ||
| super(props); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: let's write it shorter, constructor(props) {
super(props);
this.state = {gists: []};
} |
||
| this.state = {gists: []}; | ||
| } | ||
| componentDidMount() { | ||
| fetch('https://api.github.com/users/facebook/gists') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this code is not using fetch appropriately - you should be checking
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah sorry, my bad. |
||
| .then(res => res.json()) | ||
| .then(gists => this.setState({ gists })); | ||
| } | ||
| render() { | ||
| const { gists } = this.state; | ||
| return ( | ||
| <div> | ||
| <h1>Gists by facebook</h1> | ||
| {gists.map(gist => <p><a href={gist.html_url}>{gist.id}</a></p>)} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: let's put the JSX on next line. {gists.map(gist =>
<p>...</p>
)} |
||
| </div> | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| The component will perform an initial render without any of the network data. When the fetch promise resolves, it calls `setState` and the component is rerendered. | ||
|
|
||
| > **Note:** | ||
| > | ||
| > The API specification for `fetch` has not been stabilized and browser support is not quite there yet. To use `fetch` today, a [polyfill](https://github.com/github/fetch) is available for non-supporting browsers. If you're using Create React App, a polyfill is available by default. | ||
| ## Updates | ||
|
|
||
| If the props change, we might need to fetch new data for the updated props. The `componentDidUpdate` [lifecycle hook](/react/docs/react-component.html#componentdidupdate) is a good place to achieve this, since we may not need to fetch new data if the props that we're interested in have not changed. | ||
|
|
||
| Building on the previous example, we will pass the username as a prop instead and fetch new gists when it changes: | ||
|
|
||
| ```javascript{7-12,14-23} | ||
| class Gists extends React.Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = {gists: []}; | ||
| } | ||
| componentDidMount() { | ||
| const { username } = this.props; | ||
| fetch(`https://api.github.com/users/${username}/gists`) | ||
| .then(res => res.json()) | ||
| .then(gists => this.setState({ gists })); | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| const { username } = this.props; | ||
| // Make sure that the `username` prop did change before | ||
| // we initiate a network request. | ||
| if (username !== prevProps.username) { | ||
| fetch(`https://api.github.com/users/${username}/gists`) | ||
| .then(res => res.json()) | ||
| .then(gists => this.setState({ gists })); | ||
| } | ||
| } | ||
| render() { | ||
| const { username } = this.props; | ||
| const { gists } = this.state; | ||
| return ( | ||
| <div> | ||
| <h1>Gists by {username}.</h1> | ||
| {gists.map(gist => | ||
| <p><a href={gist.html_url}>{gist.id}</a></p> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| We can extract the common code in `componentDidMount` and `componentDidUpdate` into a new method, `fetchGists`, and call that in both lifecycle hooks. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should get rid of this refactor step. It adds 40 lines just to explain to the reader that if they write the same code twice, they should probably turn it into a function. The other option is for the first example to already have the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While I agree that this adds a lot to the guide, I think that this way is more in tone with the rest of the documentation. Ie show a "simplified" version first and gradually refactor towards best practices. |
||
|
|
||
| ```javascript{8,13,17-22} | ||
| class Gists extends React.Component { | ||
| constructor(props) { | ||
| super(props); | ||
| this.state = {gists: []}; | ||
| } | ||
| componentDidMount() { | ||
| this.fetchGists(); | ||
| } | ||
| componentDidUpdate(prevProps) { | ||
| if (this.props.username !== prevProps.username) { | ||
| this.fetchGists(); | ||
| } | ||
| } | ||
| fetchGists() { | ||
| const { username } = this.props; | ||
| fetch(`https://api.github.com/users/${username}/gists`) | ||
| .then(res => res.json()) | ||
| .then(gists => this.setState({ gists })); | ||
| } | ||
| render() { | ||
| const { username } = this.props; | ||
| const { gists } = this.state; | ||
| return ( | ||
| <div> | ||
| <h1>Gists by {username}.</h1> | ||
| {gists.map(gist => | ||
| <p><a href={gist.html_url}>{gist.id}</a></p> | ||
| )} | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have the last section that shows the same with async/await I think. Async/await is enabled in Create React App by default.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In that case, shouldn't all the examples feature async / await? Or is it better to demonstrate both methods? |
||
| [Try it out on CodePen.](http://codepen.io/rthor/pen/kkqrQx?editors=0010) | ||
|
|
||
| ## Pitfalls | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So basically you could not use any of the previous examples in production because they do not handle the cases where promises are fulfilled out of order, or the component unmounts. Is it even a good idea to explain those paradigms? It does not seem like a great idea to provide sample code that is actually buggy. Is there a simple way to write code that loads data asynchronously that actually does not have any of these race condition bugs?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not that I'm aware of, no. Would you prefer that we bake the pitfalls section into each example? Ie provide "bug-free" code from the get-go even if it might be a bit verbose and will probably be explaining to much in one pass for a newcomer. |
||
|
|
||
| An old promise can be pending when a newer promise fulfills. This can cause the old promise to override the results of the new one. If a promise is pending when a component is updated, the pending promise should be cancelled before a new one is created. | ||
|
||
|
|
||
| Additionally, a component can unmount while a promise is pending. To avoid unexpected behavior and memory leaks when this happens, be sure to also cancel all pending promises in the `componentWillUnmount` [lifecycle hook](/react/docs/react-component.html#componentwillunmount). | ||
|
||
|
|
||
| > **Caveat:** | ||
| > | ||
| > A standard for cancelling promises is still being worked on. Therefore, some workarounds, or 3rd party libraries, may be needed at this point. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we put it here, "refs and DOM" will need its
prevlink changed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I realized that after I made the PR. Didn't want to change it in case this guide should be elsewhere. Should we keep it here then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Going back to it, I saw that other guides in the same section don't have previous links. I removed the one in this article to keep consistency.