Now, our HoC works as expected and we can reuse it across the code base without any problems.
The question is, What should we do if we need more features?
For example, we may want to post some data to the server or fetch the data again when the props change.
Also, we may not want to load the data on componentDidMount and apply some lazy loading patterns instead.
We could obviously write all the features we need, but there is an existing library that has a lot of useful functionalities, and it is ready to be used.
The library is called react-refetch, and it is maintained by developers from Heroku. Let's see how we can use it effectively to replace our HoC.
From the previous section, we have a List component, which is a stateless functional component that can receive a collection of gists; it displays the description for each one of them:
const List = ({ data: gists }) => ( <ul> {gists.map(gist => ( <li key={gist.id}>{gist.description}</li> ))} </ul> ) List.propTypes = { data: React.PropTypes.array, }
By wrapping this component inside the withData HoC, we are able to provide data to it in a transparent way through props. We just have to enhance it, passing the URL of the endpoint.
With react-refetch, we can do the same thing; so, first of all, we install the library:
npm install react-refetch --save
Then, we import the connect function inside our module:
import { connect } from 'react-refetch'
Finally, we decorate our component using the connect HoC.
We use the partial application technique to specialize the function and reuse it:
const connectWithGists = connect(({ username }) => ({ gists: `https://api.github.com/users/${username}/gists`, }))
The preceding code needs a bit of explanation.
We use the connect function, passing a function to it as a parameter. The parameter function receives the props (and the context) as parameters so that we can create dynamic URLs based on the current properties of the component.
Our callback function returns an object where the keys are the identifiers of the request, and the values are the URLs.
The URL is not limited to being a string; we'll see later how we can add multiple parameters to the request.
At this point, we enhance our component with the function we just created, as follows:
const ListWithGists = connectWithGists(List)
We now have to make small changes to the initial component to make it work well with the new HoC.
First of all, the parameter is not called data anymore; it is called gists.
In fact, react-refetch will inject a property with the same name of the key that we specified in the connect function.
The gists prop is not the actual data, but it is a particular type of object, called PromiseState.
A PromiseState object is a synchronous representation of a Promise and it has some useful attributes such as pending or fulfilled, which we can use to show a spinner or a list of data.
There is also a rejected property that we can use in case of errors.
When the request is fulfilled, we can access the data we wanted to load using the value property and loop through it to display the gists:
const List = ({ gists }) => ( gists.fulfilled && ( <ul> {gists.value.map(gist => ( <li key={gist.id}>{gist.description}</li> ))} </ul> ) )
As soon as the stateless functional component gets rendered, we do a check to validate if the request is fulfilled; if it is, we show the list using the gists.value property.
We also have to update the propTypes and change the name of the received prop and its type:
List.propTypes = {
gists: React.PropTypes.object, }
Now that we have this library in our project, we can add more functionalities to our List component.
For example, we can add a button to start the gists.
Let's start with the UI and then add the real API call, thanks to react-refetch.
We do not want to add too many responsibilities to the List component as its role is to display the list; so, we change it to use a subcomponent for each row.
We call the new component Gist, and we are going to use it inside the loop in the following way:
const List = ({ gists }) => ( gists.fulfilled && (
<ul>
{gists.value.map(gist => (
<Gist key={gist.id} {...gist} /> ))}
</ul> ) )
We just replaced the <li> element with the Gist component, and we spread the gist object to it so that it receives single properties and becomes easier to test and maintain.
The Gist component is a stateless functional component because the starring logic is handled by react-refetch and we do not need any state or event handlers.
The component receives the description and, for now, the only difference from the previous markup is that it has a +1 button, to which we will add some functionalities soon:
const Gist = ({ description }) => ( <li>
{description} <button>+1</button> </li>
description: React.PropTypes.string, }
The URL of the endpoint to star a gist is the following:
https://api.github.com/gists/:id/star?access_token=:access_token
Here, :id is the id of the gist that we want to star, and the access token is the authentication token required to run the action.
There are different ways of getting an access token, and they are well explained in the GitHub documentation.
They are also outside the scope of this book, so we are not going to cover them in this section.
The next step is adding a function to the onClick event of the button to make the API call with the ID of the gist.
The connect function of react-refetch accepts a function as the first argument and the function has to return an object of requests, as we have previously seen.
If the values of the requests are strings, then the data is fetched as soon as the props are available.
If the value of a request key is a function instead, it gets passed into the component, and it can be fired lazily.
For example, it can be triggered when a particular event occurs. Let's delve into the code:
const token = 'access_token=123'
const connectWithStar = connect(({ id }) => ({ star: () => ({ starResponse: { url: `https://api.github.com/gists/${id}/star?${token}`, method: 'PUT', }, }), }))
First, we partially apply the collection function, and we use the id prop to compose the URL.
We then define an object of requests, where the key is star, and the value is a function that, again, returns an object of requests. In this case, the value of the key starResponse is not a simple string, but an object with two parameters: URL and method.
This is because, by default, the library fires an HTTP GET, but in this case we are required to use a PUT to star a gist.
It is now time to enhance our component:
const GistWithStar = connectWithStar(Gist)
Also, it is time to use the star function inside it to fire the request:
const Gist = ({ description, star }) => ( <li> {description} <button onClick={star}>+1</button> </li> ) Gist.propTypes = { description: React.PropTypes.string, star: React.PropTypes.func, }
As you can see, it is very simple; the component receives the star function, where star is the name of the request that we defined in the connect function. The function gets fired when the button is clicked on.
This is the final result in the browser window:
You may have noted that, thanks to react-refetch, we can keep our components stateless and unaware of the actions that they are firing.
This makes tests easier, and also we can change the implementation of the HoC without modifying the subcomponent.
Summary
The journey through data fetching in React has come to an end and now you know how to send and retreive data to and from API endpoints.
We saw how data flow works in React and why the approach it enforces can make our applications simple and clean.
We went through some of the most common patterns to make child and parent communicate using callbacks.
We learned how we can use a common parent to share data across components that are not directly connected.
In the second section, we started with a simple component, which was able to load data from GitHub, and we made it reusable, thanks to HoC.
We have now mastered the techniques that let us abstract the logic away from components so that we can make them as dumb as possible, improving their testability.
Finally, we learned how we can use react-refetch to apply data fetching patterns to our components and avoid reinventing the wheel.