Data fetching

In the preceding section, we saw the different patterns we can put in place to share data between components in the tree. It is now time to view how to fetch data in React and where the data fetching logic should be located. The examples in this section use the fetch function to make web requests, which is a modern replacement for XMLHttpRequest.

At the time of writing, it is natively implemented in Chrome and Firefox, and if you need to support different browsers, you must use the fetch polyfill by GitHub:
https://github.com/github/fetch.

We are also going to use the public GitHub APIs to load some data, and the endpoint we will use is the one that returns a list of gists, given a username, for example,
https://api.github.com/users/:username/gists.

Gists are snippets of code that can be shared easily between developers. The first component that we will build is a simple list of the gists that are created by the user, called gaearon (Dan Abramov). Let's delve into some code and create a class.

We use a class because we need to store an internal state and use life cycle methods:

  import React, { Component } from 'react';

class Gists extends Component {
...
}

Then, we define a constructor, where we initialize the state:

  constructor(props) { 
super(props);

this.state = {
gists: []
};
}

Now comes the interesting part: data fetching. There are two life cycle hooks where we can put the data fetching: componentWillMount and componentDidMount.

The first is fired before the component gets rendered for the first time and the second is fired right after the component is mounted.

Using the first seems to be the right method because we want to load the data as soon as we can, but there is a caveat. The componentWillMount function is fired on both server- and client-side rendering.

We will see the server-side rendering in detail in Chapter 8, Server-Side Rendering for Fun and Profit, but for now, you just need to know that firing an async API call when the component gets rendered on the server can give you unexpected results.

We will then use the componentDidMount hook so that we can be sure that the API endpoint is called on the browser only.

This also means that, during the first render, the list of gists is empty and we might want to show a spinner by applying one of the techniques that we saw in Chapter 2, Clean Up Your Code, which is beyond the scope of the current section.

As we said before, we are going to use the fetch function and hit the GitHub APIs to retrieve gaearon's gists:

  componentDidMount() { 
fetch('https://api.github.com/users/gaearon/gists')
.then(response => response.json())
.then(gists => this.setState({ gists }));
}

This code needs a little bit of explanation. When the componentDidMount hook gets fired, we call the fetch function against the GitHub APIs.

The fetch function returns a Promise and, when it gets resolved, we receive a response object with a JSON function that returns the JSON content of the response itself.

When the JSON is parsed and returned, we can save the raw gists inside the internal state of the component to make them available in the render method:

  render() { 
return (
<ul>
{this.state.gists.map(gist => (
<li key={gist.id}>{gist.description}</li>
))}
</ul>
);
}

The render method is simple—we just loop through the gists and map each one of them into an <li> element that shows their description.

You might have noted the key attribute of <li>. We use it for performance reasons that you will understand by the end of this book. If you try to remove the attribute, you will get a warning from React inside the browser's console.

The component works and everything is fine, but as we learned in the previous sections, we could, for example, separate the rendering logic into a subcomponent to make it more simple and testable.

Moving the component away is not a problem because we have seen how components in different parts of the application can receive the data they need from their parents.

It is very common to need to fetch data from the APIs in different parts of the codebase, and we do not want to duplicate the code. A solution we can apply to remove the data logic from the component and reuse it across the application is to create an HoC.

In this case, the HoC would load the data on behalf of the enhanced component, and it would provide the data to the child in the form of props. Let's see what it looks like.

As we know, an HoC is a function that accepts a component and some parameters and returns a new component that has some special behaviors attached to it.

We are going to use the partial application to receive the parameters first, and then the actual component as the second parameter:

  const withData = url => Component => (...);

We have called the withData function, following the with* pattern.

The function accepts the URL from which the data has to be loaded and the component that needs the data to work.

The implementation is pretty similar to the component in the preceding example, apart from the fact that the URL is now a parameter and, inside the render method, we use the child component.

The function returns a class that's defined as follows:

  class extends Component

It has a constructor to set the initial empty state.

The property we use to store the data is now called data because we are building a generic component and we do not want it to be tied to a particular object shape or collection:

  constructor(props) { 
super(props);

this.state = {
data: []
};
}

Inside the componentDidMount hook, the fetch function is fired and the data that's returned from the server is converted into JSON and stored in the state:

  componentDidMount() { 
fetch(url)
.then(response => response.json())
.then(data => this.setState({ data }));
}

It is important to note that the URL is now set using the first parameter that's received from the HoC. In this way, we can reuse it to make any API call to any endpoint.

Finally, we render the given component, spreading the props because we want our HoC to be transparent.

We also spread the state to pass the JSON data to the child component:

  render() { 
return <Component {...this.props} {...this.state} />;
}

Great! The HoC is ready. We can now wrap any components of our application and provide data to them from any URL.

Let's see how to we can use it.

First of all, we have to create a dumb component that receives the data and displays it, using the markup of the initial example:

  import React from 'react';
import { array } from 'prop-types';

const List = ({ data: gists }) => (
<ul>
{gists.map(gist => (
<li key={gist.id}>{gist.description}</li>
))}
</ul>
);

List.propTypes = {
data: array
};

We have used a stateless functional component because we do not need to store any state nor define handlers inside it, which is usually a big win.

The prop containing the response from the API is called data, which is generic and not useful, but we can easily rename it, thanks to ES6, and give it a more meaningful name.

It is now time to see how to use our withData HoC and make it pass the data down to the List component we just created. Thanks to the partial application, we can first specialize the HoC to make a specific call and use it many times, as follows:

const withGists = withData('https://api.github.com/users/gaearon/gists');

This is great because we can now wrap any component with the new withGists function and it will receive gaeron's gists without specifying the URL multiple times.

Finally, we wrap the component and get a new one:

  const ListWithGists = withGists(List);

We can now put the enhanced component anywhere within our application, and it just works.

Our withData HoC is great, but it is only able to load static URLs, while in the real world URLs often depend on parameters or props.

Unfortunately, the props are unknown at the moment we apply the HoC, so we need a hook that is called when the props are available and before making the API call.

What we can do is change the HoC to accept two types of URLs: a string, as we have done until now, and a function, which receives the component's props and returns a URL that depends on the received parameters.

That is pretty straightforward to do; we have to change the componentDidMount hook, as follows:

  componentDidMount() { 
const endpoint = typeof url === 'function'
? url(this.props)
: url

fetch(endpoint)
.then(response => response.json())
.then(data => this.setState({ data }))
}

If the URL is a function, we fire it by passing the props as parameters, while if it is a string, we use it directly.

We can now use the HoC in the following way:

  const withGists = withData( 
props => `https://api.github.com/users/${props.username}/gists`
);

Here, the username for loading the gists can be set in the props of the component:

  <ListWithGists username="gaearon" />
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset