Skip to content
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

[docs] Loading Data Asynchronously #8883

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

155 changes: 155 additions & 0 deletions docs/docs/loading-data-asynchronously.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
---
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.

## 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 [axios](https://github.com/mzabriskie/axios) to retrieve information about Facebook's Repos on GitHub and store them in the state. So first install it:
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you say "repositories" instead of "Repos"? I think a lot of people will not instinctively know what a "repo" is.


```
$ npm install axios --save
```

And you can use it in your project:

```javascript{1,9-13}
import axios from 'axios';

class Repos extends React.Component {
constructor(props) {
super(props);
this.state = {repos: []};
}

componentDidMount() {
axios.get('https://api.github.com/users/facebook/repos')
.then(response => this.setState({ repos: response.data }))
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens here if the component has unmounted before this request completes?

Copy link

@stereobooster stereobooster Jun 27, 2017

Choose a reason for hiding this comment

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

I suppose we need to add cancel to componentWillUnmount.

componentDidMount() {
   let CancelToken = axios.CancelToken;
   let cancel
   let cancelToken = CancelToken(c => cancel = c)
   
   this.setState({ cancel: cancel })

   axios.get('https://api.github.com/users/facebook/repos', { cancelToken })
     .then(response => this.setState({ repos: response.data }))
     .catch(error => console.log(error));
 }

componentWillUnmount() {
  this.state.cancel()
}

UPD there is example with cancelation lower

.catch(error => console.log(error));
}

render() {
return (
<div>
<h1>Repos by facebook</h1>
{this.state.repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
}
```

The component will perform an initial render without any of the network data. When the axios promise resolves, it calls `setState` and the component is rerendered.

[Try it on CodePen.](http://codepen.io/dashtinejad/pen/wgzEXJ?editors=0011)

## 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 repos when it changes:

```javascript{7-11,,13-15,17-21,35,38,44-47,53}
class Repos extends React.Component {
constructor(props) {
super(props);
this.state = {repos: []};

Choose a reason for hiding this comment

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

what about loading state e.g. spinner?

}

fetchRepos() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this fetchRepos function supposed to have a particular behavior in terms of doing things in the right order? Does axios guarantee that?

axios.get(`https://api.github.com/users/${this.props.username}/repos`)
.then(response => this.setState({ repos: response.data }))
.catch(error => console.log(error));
}

componentDidMount() {
this.fetchRepos();
}

componentDidUpdate(prevProps) {
if (this.props.username != prevProps.username) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Won't this lead to, if you first load repos for foo, and then for bar, on the immediate load of bar it will show the foo repos under a header that says "bar repo"?

this.fetchRepos();
}
}

render() {
return (
<div>
<h1>Repos by {this.props.username}</h1>
{this.state.repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
}

class App extends React.Component {
constructor(props) {
super(props);
this.state = {username: 'facebook'};
}

render() {
return (
<div>
<button onClick={() => this.setState({username: 'facebook'})}>Facebook</button>
<button onClick={() => this.setState({username: 'microsoft'})}>Microsoft</button>
<button onClick={() => this.setState({username: 'google'})}>Google</button>
<Repos username={this.state.username} />
</div>
);
}
}

ReactDOM.render(<App />, document.getElementById('app'))
```

[Try it on CodePen.](http://codepen.io/dashtinejad/pen/zNpzVW?editors=0011)

## Cancellation

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.
Some data fetching APIs allow you to cancel requests, and for axios, we use [Cancellation](https://github.com/mzabriskie/axios#cancellation) by token:

```javascript{5-8,10-11,14-15,19-23}
class Repos extends React.Component {
// removed for brevity

fetchRepos() {
// cancel the previous request
if (typeof this._source != typeof undefined) {
this._source.cancel('Operation canceled due to new request.')
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible that this._source has already completed by the time this line of code runs? That seems possible to me.

}

// save the new request for cancellation
this._source = axios.CancelToken.source();

axios.get(`https://api.github.com/users/${this.props.username}/repos`,
// cancel token used by axios
{ cancelToken: this._source.token }
)
.then(response => this.setState({ repos: response.data }))
.catch(error => {
if (axios.isCancel(error)) {
console.log('Request canceled', error);
} else {
console.log(error);

Choose a reason for hiding this comment

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

state error - show error message. Not that much code, but will encourage programmers to implement error messages and think of all states of component with async load

}
});
}

// removed for brevity
}
```

[Try it on CodePen.](http://codepen.io/dashtinejad/pen/Lxejpq?editors=0011)

[You can clone the whole sourcecode from GitHub](https://github.com/dashtinejad/react-ajax-axios).