Skip to content
3 changes: 2 additions & 1 deletion docs/_data/nav_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
title: JSX In Depth
- id: typechecking-with-proptypes
title: Typechecking With PropTypes
- id: loading-data-asynchronously
title: Loading Data Asynchronously
- id: refs-and-the-dom
Copy link
Collaborator

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 prev link changed.

Copy link
Contributor Author

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?

Copy link
Contributor Author

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.

title: Refs and the DOM
- id: optimizing-performance
Expand Down Expand Up @@ -80,4 +82,3 @@
title: Shallow Compare
- id: two-way-binding-helpers
title: Two-way Binding Helpers

139 changes: 139 additions & 0 deletions docs/docs/loading-data-asynchronously.md
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).

Copy link
Collaborator

Choose a reason for hiding this comment

The 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The 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')
Copy link
Contributor

Choose a reason for hiding this comment

The 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 response.ok to see if there is an error. As is, if there is a 4xx or 5xx error I think it will fail in some weird way during parsing. Even just rejecting a promise would be OK here IMO but fetch actually does not reject the promise on server failure.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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>)}
Copy link
Collaborator

Choose a reason for hiding this comment

The 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.
Copy link

Choose a reason for hiding this comment

The 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 fetchGists method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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>
);
}
}
```

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

@rthor rthor Nov 19, 2016

Choose a reason for hiding this comment

The 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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically promises can't be "cancelled" so this might read confusing. Maybe "the result of the first promise should be ignored".


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).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, you can't cancel promises. Also it isn't strictly causing memory leaks, just unnecessary requests.
Maybe:

Additionally, a component can unmount while a promise is pending. React warns you if you call setState() on unmounted components to prevent memory leaks. Some data fetching APIs allow you to cancel requests, and this is preferable when component unmounts. For APIs such as fetch() that don't offer a cancellation mechanism, you would need to keep track of whether component is mounted to avoid seeing warnings. Here is how we could implement this:

Then show how to set this.isUnmounted = true; in componentWillUnmount() and change fetch() callback to exit if this flag is set or if current username in props doesn't match the one we were fetching. This would solve both caveats.

As a final example you could then show a more concise way of doing the same with a library that supports cancellation like axios.


> **Caveat:**
>
> A standard for cancelling promises is still being worked on. Therefore, some workarounds, or 3rd party libraries, may be needed at this point.