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

Add docs for using Promises and making AJAX requests #531

Closed
wants to merge 9 commits into from
70 changes: 53 additions & 17 deletions template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -487,55 +487,91 @@ __About Promises:__ This project also includes a [Promise polyfill](https://gith

You can make a GET request like this:
```javascript
class MyComponent extends Component {
import React, { Component, PropTypes } from 'react';

const { string } = PropTypes;

class RepoList extends Component {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's keep it called App so it's obvious this code can be copy and pasted into App.js.

static propTypes = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's remove propTypes. Not essential to this example and introduces class properties which we don't want for now.

user: string.isRequired,
}

constructor(props) {
super(props);
this.state = {};
super(props)
this.state = { repos: [] };
}

componentDidMount() {
this.fetchGists();
this.fetchRepos(this.props.user);
Copy link
Contributor

Choose a reason for hiding this comment

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

If the person copies and pastes this component into App.js, user prop will be undefined. Can you make it so this is actually copy and pasteable with no modifications?

I would suggest that, rather than display repos by user, let's display stargazers of facebook/react repo. Then we can hardcode it.

}

fetchGists() {
fetch('https://api.github.com/users/octocat/gists')
fetchRepos(user) {
fetch(`https://api.github.com/users/${user}/repos`)
Copy link
Contributor

Choose a reason for hiding this comment

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

Another subtle issue here is that fetch() currently offers no cancellation API. React warns if you call setState() on an unmounted component so this will warn sometimes and potentially confuse people.

Let's set a boolean instance field called hasUnmounted in componentWillUnmount. Then check that field before calling setState.

.then((response) => response.json())
.then((gists) => this.setState({ gists }))
.then((repos) => this.setState({ repos }))
.catch((error) => console.log('Error', error));
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a subtle problem here. If setState() throws (for example due to an error in render()), we will get into the catch handler. It is then easy to miss or ignore that error, and now React is in an inconsistent state.

Instead, let's put catch() before the second then(). This way we only catch network errors. We want errors in setState() to stay unhandled.

Let's make catch() return an empty array. Then we can safely use its result in setState() whether it succeeded or failed.

Copy link
Contributor

@fson fson Sep 6, 2016

Choose a reason for hiding this comment

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

Also note that fetch doesn't reject/throw for an error status, so if GitHub is down and you get a 503 status, or if you get rate limited and they return 403, none of these errors will end up in the catch() – setState() gets called with the parsed JSON body of the error response.

So to handle HTTP errors, you'll also have to check response.status, e.g. response.status >= 200 && response.status < 300 (response.ok is a handy shortcut for that), and treat a non-OK status as an error.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yep, @insin mentioned this later in a comment too. We should handle both !ok and actual errors in the same way, that is, by showing the message.

}

render() {
return <UserGists gists={this.state.gists} />;
return (
<ul>
{this.state.repos.map((repo) => (
<li key={repo.full_name}>
<a href={repo.html_url}>{repo.full_name}</a>
</li>
))}
</ul>
);
}
}

export default RepoList;
```

You can also use the `async/await` syntax to fetch data. [Here](https://zeit.co/blog/async-and-await) is an introduction to it.
```javascript
class MyComponent extends Component {
import React, { Component, PropTypes } from 'react';

const { string } = PropTypes;

class RepoList extends Component {
static propTypes = {
user: string.isRequired,
}

constructor(props) {
super(props);
this.state = {};
super(props)
this.state = { repos: [] };
}

componentDidMount() {
this.fetchGists();
this.fetchRepos(this.props.user);
}

async fetchGists() {
async fetchRepos(user) {
try {
const response = await fetch('https://api.github.com/users/octocat/gists');
const gists = await response.json();
this.setState({ gists });
const response = await fetch(`https://api.github.com/users/${user}/repos`);
Copy link
Contributor

Choose a reason for hiding this comment

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

This would need to be adjusted to match the behavior I mentioned above.

const repos = await response.json();
this.setState({ repos });
} catch(error) {
console.log('Error', error);
}
}

render() {
return <UserGists gists={this.state.gists} />;
return (
<ul>
{this.state.repos.map((repo) => (
<li key={repo.full_name}>
<a href={repo.html_url}>{repo.full_name}</a>
</li>
))}
</ul>
);
}
}

export default RepoList;
```

## Integrating with a Node Backend
Expand Down