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

208 changes: 208 additions & 0 deletions docs/docs/loading-data-asynchronously.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
---
id: loading-data-asynchronously
title: Loading Data Asynchronously
permalink: docs/loading-data-asynchronously.html
---

React has no special capabilities for dealing with asynchronous network requests and a third party library or browser API is needed to perform them. If a component needs to have its UI respond to new data arriving, it has to call `setState` to rerender itself.

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.

## Initial Render

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

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)

We can simplify the `fetchGists` method by using the [`async / await`](https://tc39.github.io/ecmascript-asyncawait/) feature:
Copy link

Choose a reason for hiding this comment

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

We should get rid of all this stuff about async/await, too. It's really out of scope. Our readers are busy learning React, and they either already know about async/await, or shouldn't worry about learning about async/await right here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point. But Dan wanted this in the guide.

Copy link

Choose a reason for hiding this comment

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

Async/await is enabled in Create React App by default.

@gaearon: would you mind elaborating on why we should include a section on async/await?

It seems like something better suited for async/await documentation, since it's a pretty trivial example.

Copy link
Collaborator

Choose a reason for hiding this comment

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

It seems like something better suited for async/await documentation, since it's a pretty trivial example.

Not sure what you mean by async/await documentation. Is this something we would write? Which example is trivial?

I think that if we don't show async/await "works" inside components then many people won't realize that. I've seen many surprised reactions to async componentDidMount() because people think it's something React has to "support" as opposed to just being a language feature. So I think it's nice to provide at least a short section giving you enough clues to get started with using it.


```javascript{1,3-4}
async fetchGists() {
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 the best practice is actually to write some helper that uses fetch. Then the nature of this async/await example is different. I think it's still handy to have an example with async/await here though.

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.

Helper to handle errors and bad requests then? eg

function safeFetch(url, options) {
  return fetch(url, options).then(response => {
    if (!response.ok) {
      throw new Error(response.statusText)
    }    
    return response.json()
  })
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So the async code would be:

async fetchGists() {
  const { username } = this.props;
  try {
    const gists = await safeFetch(`https://api.github.com/users/${username}/gists`);
    this.setState({gists});
  } catch (error) {
    // Request failed...
  }
}

const { username } = this.props;
const data = await fetch(`https://api.github.com/users/${username}/gists`);
this.setState({gists: await data.json()});
}
```

[Try it out on CodePen.](https://codepen.io/rthor/pen/xEoWod?editors=0010)

> **Note:**
>
> `async / await` is still a proposal for the ECMAScript spec and therefore hasn't been implemented in most browsers. To use it today, a [Babel](http://babeljs.io/docs/plugins/transform-async-to-generator/) (or similar) transform is needed. If you're using Create React App, it works by default.
## 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 result of the new one. If a promise is pending when a component is updated, the result of the first promise should be ignored before a new one is created.

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 a component unmounts. For APIs such as `fetch` that don't offer a cancellation mechanism, you need to keep track of whether the component is mounted to avoid seeing warnings. Here is how we could implement this:
Copy link

Choose a reason for hiding this comment

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

We should first mention that the best solution to this issue is to resolve whatever is causing this unmounting issue. API calls should be happening as high up in your component tree as possible. If your top-level component is so far up the tree that you can't make the API call there and pass down the data, that's when I'd start considering keeping my state outside of React.

Copy link
Contributor

Choose a reason for hiding this comment

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

So what's your suggested solution to callback race conditions - just don't unmount a component that's loading data? I don't like having a "pitfalls" section that just states that something is a problem for everyone without showing how to solve it - we should just not have a "pitfalls" part and instead explain "how to do things right".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not sure what you mean by resolving the unmounting issue as it can be a completely correct behaviour and has nothing to do with the request itself.

API calls should be happening as high up in your component tree as possible.

I would argue against this approach. If data is passed down the entire tree, without defining componentShouldUpdate at every step, the entire app will be rerendering with every response.

Copy link

Choose a reason for hiding this comment

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

I was imagining some scenario that could be fixed by restructuring your code. For instance, if it'd be possible to move the API call up to the immediate parent, and pass the data down to the component instead. Was I way off base there? :P

Cancellation is obviously the solution when it's unavoidable.

Also, out of curiosity, what are some common use cases where this occurs for people? I understand how it could happen, but I never really encounter it. Route changes was one that came to mind.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Route changes is the easiest case, yes. Go forward, then go back, oops, the wrong thing has arrived.


```javascript{8,15,19-21,23,26,29-34}
class Gists extends React.Component {
constructor(props) {
super(props);
this.state = { gists: [] };
}
componentDidMount() {
this.fetchGists(this.props.username);
}
componentDidUpdate(prevProps) {
// Make sure that the `username` prop did change before
Copy link

Choose a reason for hiding this comment

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

We probably only need these comments in the example for the componentDidUpdate example, but no biggie either way.

// we initiate a network request.
if (this.props.username !== prevProps.username) {
this.fetchGists(this.props.username);
}
}
componentWillUnmount() {
this.isUnmounted = true;
}
fetchGists(username) {
fetch(`https://api.github.com/users/${username}/gists`)
.then(data => data.json())
.then(gists => this.handleFetchSuccess(username, gists));
}
handleFetchSuccess(username, gists) {
if (this.isUnmounted || username !== this.props.username) {
return;
}
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>
);
}
}
```

[Try it out on CodePen.](http://codepen.io/rthor/pen/edweqz?editors=0010)