Skip to content

Add docs for using Promises and making AJAX requests #531

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

Closed
wants to merge 9 commits into from
87 changes: 87 additions & 0 deletions template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ You can find the most recent version of this guide [here](https://github.com/fac
- [Adding Flow](#adding-flow)
- [Adding Custom Environment Variables](#adding-custom-environment-variables)
- [Can I Use Decorators?](#can-i-use-decorators)
- [Fetching AJAX Requests](#fetching-ajax-requests)
- [Integrating with a Node Backend](#integrating-with-a-node-backend)
- [Proxying API Requests in Development](#proxying-api-requests-in-development)
- [Using HTTPS in Development](#using-https-in-development)
Expand Down Expand Up @@ -475,6 +476,92 @@ Please refer to these two threads for reference:

Create React App will add decorator support when the specification advances to a stable stage.

## Fetching AJAX Requests

This project includes a [fetch polyfill](https://github.com/github/fetch), which makes it easier to make web requests.

The global `fetch` function allows to easily makes AJAX requests. It takes in a URL as an input and returns a `Promise` that resolves to a `Response` object. You can find more information about `fetch` [here](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch).


__About Promises:__ This project also includes a [Promise polyfill](https://github.com/then/promise) which provides a full implementation of Promises/A+. A Promise represents the eventual result of an asynchronous operation, you can find more information about Promises [here](https://www.promisejs.org/) and [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise).

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

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

componentDidMount() {
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.

}

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((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 (
<ul>
{this.state.repos.map((repo) => (
<li key={repo.full_name}>
<a href={repo.html_url}>{repo.full_name}</a>
</li>
))}
</ul>
);
}
}

export default App;
```

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
import React, { Component } from 'react';

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

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

async fetchRepos(user) {
try {
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 (
<ul>
{this.state.repos.map((repo) => (
<li key={repo.full_name}>
<a href={repo.html_url}>{repo.full_name}</a>
</li>
))}
</ul>
);
}
}

export default App;
```

## Integrating with a Node Backend

Check out [this tutorial](https://www.fullstackreact.com/articles/using-create-react-app-with-a-server/) for instructions on integrating an app with a Node backend running on another port, and using `fetch()` to access it. You can find the companion GitHub repository [here](https://github.com/fullstackreact/food-lookup-demo).
Expand Down