2016-11-09
How to use React's higher-order components
When React first hit the scene, it brought with it a new way of developing front-end architectures. It was regarded as the “View” in Model-View-Controller.
Over time, core contributors to the React ecosystem have latched onto the container/presentational pattern.
In this post, you’ll learn more about the container/presentational pattern and how you can use it to create React components.
Before I go any further, let’s clarify some of the jargon:
- A container is a component that fetches and/or transforms data.
- A presentational component presents/displays the data it’s passed through its props. Nothing else.
- A container acts as a higher-order component. Composing higher-order components together allows you to separate those two layers. This prevents you from mudding your presentational layer with unnecessary logic.
If you want, you can follow along using ESNextbin.
After navigating to that page, at the top you should see three tabs: CODE, HTML, and PACKAGE. Click on PACKAGE. After navigating to that tab add react and react-dom as dependencies.
{
"name": "esnextbin-sketch",
"version": "0.0.0",
"dependencies": {
"react" : "latest",
"react-dom": "latest"
}
}
Now click on the CODE tab and then import react and render from react-dom.
import { default as React } from "react";
import { render } from "react-dom";
Now let’s create a stateless functional component that renders ‘Hello, world!’
const Presentational = () => <div>Hello, world!</div>;
After, you just need to mount that component to the DOM using render.
render(<Presentational />, document.querySelector("#root"));
You still haven’t created the root element in the HTML, so let’s do that now. All you are doing is adding a div with the id of root.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>ESNextbin Sketch</title>
<! — put additional styles and scripts here →
</head>
<body>
// Notice something different
<div id='root'></div>
</body>
</html>
Now for the moment of truth. Click EXECUTE in the top right corner of the page. You should see Hello, world! on the screen.
You now have a presentational component. But you also need a container component to fetch your data.
Let’s create a container component above the presentational one. To create the container you will be using React’s class component. For this reason, you will need to import it at the top of the page.
import { default as React, Component } from "react";
At this point you can get busy with creating the container component.
class Container extends Component {
state = {
data: []
}
componentDidMount() {
fetch('[https://fcctop100.herokuapp.com/api/fccusers/top/alltime'](https://fcctop100.herokuapp.com/api/fccusers/top/alltime%27))
.then(response => response.json())
.then(data => this.setState({ data }))
}
render() {
return <Presentational data={this.state.data} />
}
}
At the top, you have the state that is an empty array. You are using a lifecycle method called componentDidMount where you will fetch the data.
When the response returns you then update the state with the data. The data is then passed as a prop to the presentational component. This means you will need to update your presentational component as well.
const Presentational = ({ data }) => <div>{JSON.stringify(data)}</div>;
Here I am using destructuring to pluck data off the props object. Otherwise it would look like this:
const Presentational = (props) => <div>{JSON.stringify(props.data)}</div>;
These changes force you to update which component gets mounted. You now need to render the container component.
render(<Container />, document.querySelector("#root"));
When you click on EXECUTE you should see a hole heap of data overflowing on your screen. Oh my!
What you have done here is abstract away the container component and everything it does. But it’s not flexible. You have to place your presentational component inside the container. Is there another way of doing it that’s more flexible? Enter the higher-order component.
A higher-order component is a function that takes a component and returns a new component. How would that look in practice. Let’s create a function that takes a component and returns a new component with the data from the FCC API. You will be updating the container component to do this.
const container = Presentational =>
class extends Component {
state = {
data: []
}
componentDidMount() {
fetch('[https://fcctop100.herokuapp.com/api/fccusers/top/alltime'](https://fcctop100.herokuapp.com/api/fccusers/top/alltime%27))
.then(response => response.json())
.then(data => this.setState({ data }))
}
render() {
return <Presentational data={this.state.data} />
}
}
Nothing too different here. There is, however, one extra step you have to perform. You need to compose this container and presentational component together. I am just calling container with the presentational component as an argument. This is added just below your presentational component.
const HigherOrderComponent = container(Presentational);
Now all you have to do is render the higher-order component.
render(<HigherOrderComponent />, document.querySelector("#root"));
You see what you did there? Now you can use the container with any component, not just the presentational component!
Let’s take this one step further though. Let’s call another function that tells the container component what data to fetch. I think that would make the container even more flexible.
The new container will look something like this:
const container = (endpoint) => (Presentational) =>
class extends Component {
state = {
data: [],
};
componentDidMount() {
fetch(endpoint)
.then((response) => response.json())
.then((data) => this.setState({ data }));
}
render() {
return <Presentational data={this.state.data} />;
}
};
Now you will need to update the higher-order component to handle the new call to the endpoint.
const HigherOrderComponent = container(
'[https://fcctop100.herokuapp.com/api/fccusers/top/alltime'
)(Presentational)](https://fcctop100.herokuapp.com/api/fccusers/top/alltime%27%29%28Presentational%29)
If that looks a little off to you then you have good eye. What if you could abstract that out even further? Enter recompose!
Let’s pause for a moment now and cover what the idea of compose is. To understand compose you need to understand mapping.
Mapping in computer programming is when you take a value and transform it into another value. Which is basically every function ever created! Let me give you an example for good measure.
function double(x) {
return x * 2;
}
const doubled = double(2);
Now let’s say I want to make another transformation, for example triple. How would I do that?
function triple(x) {
return x * 3;
}
function double(x) {
return x * 2;
}
const messedAroundAndGotATripleDouble = triple(double(2));
Simple enough, right? What if I wanted to make another transformation, and another, and another. It would get quite long. With the help of a compose function you can make the code not only more readable but more composable.
function compose(f, g) {
return function (x) {
return f(g(x));
};
}
function triple(x) {
return x * 3;
}
function double(x) {
return x * 2;
}
const tripleThenDouble = compose(triple, double);
const messedAroundAndGotATripleDouble = tripleThenDouble(2);
This is a simple example, but it shows how you can make many transformations on the same initial value.
Now imagine the initial value is the presentational component while the container is one of the functions that transforms the initial value. Mind blown!
To start using recompose you need to pull in the recompose library. Under PACKAGES let’s add the recompose library.
{
"name": "esnextbin-sketch",
"version": "0.0.0",
"dependencies": {
"react": "15.3.2",
"react-dom": "15.3.2",
"recompose": "latest",
"babel-runtime": "6.11.6"
}
}
Going back to the CODE let’s import compose from recompose.
import { compose } from "recompose";
Then you can use compose to further abstract the higher-order component.
const HigherOrderComponent = compose(
container(
'[https://fcctop100.herokuapp.com/api/fccusers/top/alltime'](https://fcctop100.herokuapp.com/api/fccusers/top/alltime%27)
)
)
And you can be more declarative about what the entire component is doing.
const FetchAndDisplayFCCData = HigherOrderComponent(Presentational);
render(<FetchAndDisplayFCCData />, document.querySelector("#root"));
This has finally allowed you to create components that are versatile and flexible. You can even test your presentational components separately from higher-order components. Higher-order components FTW!
For kicks, Let’s update the presentational component to render a list of items instead of a big blob of data.
const Presentational = ({ data }) => (
<ul>
{data.map((v, k) => (
<li key={k}>{v.username}</li>
))}
</ul>
);
Whew! That was a lot. If you want to see the finished product take a look at the gist I created here.
For more information visit Andrew Clark’s great React utility library called recompose to learn more!
This article has Webmentions