Higher order components (HOCs) are functions that take a component and return another. The returned component is altered, typically, to add a feature or enrich it with more functionality than it originally contained.
I always knew that higher order components were a thing and important to know but no one could convince me why they were important. Therefore I wrote this post to explain in my own words:
- why we need them by way of a quick introduction to imperative vs declarative programming
- how HOCs can make you a better programmer even if you don’t use them directly e.g. Higher order functions (HOFs)
- using an example of data loading HOC, how Recompose can take them to the next level
Live example of Higher Order Components used in this post.
Code sandbox of Higher Order Component code used in this post.
Imperative vs Declarative Programming
Declarative programming is the CORE philosophy behind HOCs. Getting straight to the point, we yearn for writing code that looks less like code. Why? As your code base gets larger, more complicated and used by more than one person or team I found I ended up reading more and more code instead of getting on with implementing a feature or fixing a feature. When someone created a feature, I’d still have to stare at their code to understand exactly what it did unless they explained it to me or had some reasonable documentation (but even that wasn’t always enough). So I started using HOCs without even understanding their benefits and I found that I read less code and got more done. I wasn’t writing any less or more code, I just spent less time staring at code. Hmmmmmm…… What I was really experiencing was the shift from imperative to declarative programming.
Imperative Programming is how we traditionally write code. It looks like code and smells like code. It uses things like iterators, variables and other constructs to explain how to get what we want
Declarative Programming is like writing a story or specifying a series of phrases to describe our intentions. We use pre-defined functions to explain what we want
For example say we have a list of movies:
const movies = [
{
id: 1,
title: ‘Star Wars’,
rating: 5,
genre: ‘Sci-fi’,
description: ‘lots of explosions and good guy bad guy action’
},
…
// (other movies)
];
Imperative Programming
Look at the following code written in an imperative way. See if you can understand what it’s doing.
const resultMovies = [];
for (let i = 0; i < movies.length; i++) {
const movie = movies[i];
if (movie.rating === 5 && movie.genre === ‘Sci-fi’) {
resultMovies.push({
id: movie.id,
title: movie.title
});
}
}
console.log(resultMovies);
// => [{id: 1, title: ‘Star Wars’}]
Did that feel a bit overwhelming at first? Did you have to stare at the code for a while…? Right, so in the above code I’m iterating over a bunch of movies and getting a list of those with a rating of 5 and genre of ‘Sci-fi’. And by the way I only want the id and title of each movie.
Declarative Programming
OK now look at the following code that’s doing the same thing but in a declarative way:
const getMoviesWithRatingOf5 = movies => movies.filter(movie => movie.rating === 5);
const getSciFiMovies = movies => movies.filter(movie => movie.genre === ‘Sci-fi’);
const getMovieTitles = movies => movies.map(movie => ({id: movie.id, title: movie.title}));
getMovieTitles(getSciFiMovies(getMoviesWithRatingOf5(movies)));
// => [{id: 1, title: ‘Star Wars’}]
That’s way easier to read and much easier to code for! With regards to:
getMovieTitles(getSciFiMovies(getMoviesWithRatingOf5(movies)));
// => [{id: 1, title: ‘Star Wars’}]
we first passed our movies to getMoviesWithRatingOf5. The resultant movies were then passed to the getSciFiMovies and finally getMovieTitles iterated over the resultant movies and returned only their id and title.
Provided we have a bunch of functions like getMoviesWithRatingOf5, we just have to combine them in the right order to get what we want. We are describing/explaining what we want.
“We want code that describes what we want, not how to get it”
This is also referred to as Functional Programming and is CORE to understanding HOCs. This is a pretty cool subject and if you would like to know more, check out my other post Getting started with Functional Programming and Ramda.
Higher Order Functions
So far I’ve shown how writing in a declarative way makes our code easier to read and write. But I still haven’t associated it with HOCs. Believe it or not, we just used higher order functions or (HOFs) in that last example called filter and map. These do exactly the same as HOCs but with functions instead of components. Like I said earlier, knowing about HOCs will make you a better programmer. If you understand the core patterns of HOCs you can make higher order anythings resulting in more readable and maintainable code! Let’s take a very brief look at HOFs because you’re going to be a lot more familiar with them than you think and they are therefore an excellent segue into HOCs…
“Higher order functions are functions that take a function and return a function”
E.g. the map HOF takes a function and returns another function that iterates over a collection applying that specified function against each collection item. In our example above we iterated over a collection of movies and used the map function in ‘getMovieTitles’ to only return id and title for each movie. This is an example of what the implementation of the map HOC might look like (just to show there’s no smoke and mirrors):
const map = (fn, array) => {
const mappedArray = []
for (let i = 0; i < array.length; i++) {
mappedArray.push(
// apply fn with the current element of the array
fn(array[i])
)
}
return mappedArray
}
Higher Order Components
Higher order components are essentially the same thing but with components.
“Higher order components are functions that take a component and return a component”
Let’s look at a very common scenario in plain Javascript (using React) where we want to display some person’s details. Their details have to be loaded from a backend service so we need to show a loading animation while that person waits so they know the application is still working.
Here is our React component that displays a user’s details:
const MyDetails = ({ name, interests, gender, height }) => (
<div className=”User”>
<h3>{name}</h3>
<ul>
<li>Interests: {interests}</li>
<li>Gender: {gender}</li>
<li>Height: {height}</li>
</ul>
</div>
);
Here is our function for asynchronously fetching our person’s details from a backend service (yes you got me… it’s a setTimeout function to simulate a back end call to a non-existent service which is good enough for this example).
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(
() =>
resolve({
name: “Davin Ryan”,
interests: “Javascript, React and Recompose”,
gender: “male”,
height: “183cm”
}),
2000
);
});
};
and finally this is our HOC that will:
- take our above backend function and user details component
- show a loading screen until the data is fetched
- populate and render our component with that backend data
import React from ‘react’;
import ‘./WithLoadingSpinner.css’;
export const WithLoadingSpinner = fetchData => WrappedComponent => {
const Spinner = () =>
<div className=’spinner’></div>
;
return class extends React.Component {
constructor(props) {
super(props);
this.state = {loading: true};
}
componentDidMount() {
fetchData().then((data) =>
this.setState({loading: false, …data}));
}
render() {
const {loading, …data} = this.state;
return (
<div>
{loading ? (<Spinner/>) : (<WrappedComponent {…data} />)}
</div>
);
}
}
};
This is how we use the ‘WithLoadingSpinner’ HOC to create a more useful ‘MyDetailsWithLoadingSpinner’ component.
const LoadingSpinnerExample = () => {
const MyDetailsWithLoadingSpinner = WithLoadingSpinner(fetchData)(MyDetails);
return (
<div>
<h2>Loading Spinner Example</h2>
<MyDetailsWithLoadingSpinner />
</div>
);
};
If we were to load this application, this is what we’ll get while data is being fetched
and this is what we’ll get once that data has been fetched and the MyDetails component is displayed.
A live example can be found here.
You can also examine and run the code used throughout the post from this code sandbox.
Great so let’s break up that HOC and look at it in more detail…
Part 1 – Define the HOC
import React from ‘react’;
import ‘./WithLoadingSpinner.css’;
export const WithLoadingSpinner = fetchData => WrappedComponent => {
After importing React and some CSS for the spinner animation we create a HOC component called ‘WithLoadingSpinner’. WithLoadingSpinner is a function that takes a parameter called ‘fetchData’ (the function that fetches data). WithLoadingSpinner is now a function that returns another function that takes a parameter called ‘WrappedComponent’ (the ‘MyDetails’ component that will display data from the fetchData function).
Tip – Just like numbers, strings, booleans, etc., functions are values. That means that you can pass functions around like any other data. You can pass a function as an argument to another function e.g the function that returns a function that returns ‘some data’
const getData = () => () => ‘some data’;
is called like this:
getData()();
Part 2 – Define the spinner
const Spinner = () =>
<div className=’spinner’></div>
;
This is the spinner that will show while we call ‘fetchData’
Part 3 – Return the new enriched component
return class extends React.Component {
constructor(props) {
super(props);
this.state = {loading: true};
}
componentDidMount() {
fetchData().then((data) =>
this.setState({loading: false, …data}));
}
render() {
const {loading, …data} = this.state;
return (
<div>
{loading ? (<Spinner/>) : (<WrappedComponent {…data} />)}
</div>
);
}
}
Here we return an ‘anonymous React component’ whose initial state is that of ‘loading’. The render function will display the spinner while we are in a state of ‘loading’. After the spinner is mounted, ‘componentDidMount’ will call ‘fetchData’ and once data is retrieved, the component’s loading state will end, the component will re-render and display the ‘WrappedComponent’ or ‘MyDetails’ component with the data passed to it as properties. Yay!
Higher Order Components with Recompose
The above HOC was looking rather complicated…. let’s see what it looks like when we re-write the WithLoadingSpinner HOC using Recompose
import React from ‘react’;
import { compose, lifecycle, branch, renderComponent } from ‘recompose’;
import ‘./WithLoadingSpinner.css’;
export const WithLoadingSpinner = fetchData => WrappedComponent => {
const Spinner = () =>
<div className=’spinner’></div>
;
const withData = lifecycle({
state: { loading: true },
componentDidMount() {
fetchData().then((data) =>
this.setState({ loading: false, …data }));
}
});
const withSpinnerWhileLoading = branch(
({ loading }) => loading,
renderComponent(Spinner)
);
return compose(
withData,
withSpinnerWhileLoading
)(WrappedComponent);
}
That seems much better. Let’s break it down into more detail…
The first and second parts are the same as our non-recompose example so let’s skip to the third part.
Part 3 – Define a bunch of HOCs that create our final WithLoadingSpinner HOC
const withData = lifecycle({
state: { loading: true },
componentDidMount() {
fetchData().then((data) =>
this.setState({ loading: false, …data }));
}
});
const withSpinnerWhileLoading = branch(
({ loading }) => loading,
renderComponent(Spinner)
);
return compose(
withData,
withSpinnerWhileLoading
)(WrappedComponent);
So instead of returning an enriched React component we use a couple of HOCs in a chain. Think about it this way. The last return statement could have been written like this and had the same effect:
return withSpinnerWhileLoading(withData(WrappedComponent));
A quick summary (starting at the bottom return statement):
- compose HOF – a Recompose convenience function to make it easier to read our chaining/composing of HOCs withData and withSpinnerWhileLoading with our target component WrappedComponent i.e MyDetails
- withData HOC – This little guy will take our MyDetails component and add/modify its lifecycle function componentDidMount to include code to call ‘fetchData’ just like in the previous example
- withSpinnerWhileLoading HOC
- withData uses Recompose’s lifecycle HOC to add life cycle behaviour. Any state changes made in a lifecycle method, by using setState, will be propagated to the wrapped component as properties. In this case withSpinnerWhileLoading receives the properties e.g. { loading: false, …data }
- Recompose’s branch test function takes (in this example) two arguments. The first argument is a test which checks if the ‘loading’ component property is true or false and the second argument is a component to return if the test function resolves true. When ‘loading’ is true we use Recompose’s renderComponent which takes a loading spinner component and returns a HOC version of it which is returned instead of ‘WrappedComponent’. When ‘loading’ is false however, ‘WrappedComponent’ is returned instead
Testing Higher Order Components
Testing HOCs is no different to testing normal components. You use the HOC to take a mock/dummy component into an enhanced component and then test that enhanced component using Enzyme e.g.
describe(‘WithLoadingSpinner Test Suite’, () => {
// ‘fetchData’ could be a mocked function that gets data like our example above using setTimeout.
const MyComponent = () =>
<div></div>
;
const WithLoadingSpinnerComponent = WithLoadingSpinner(fetchData)(MyDetails);
it(‘Test to make sure MyDetails is populated correctly’, () => {
const wrapper = shallow(<WithLoadingSpinnerComponent />);
const props = wrapper.props();
expect(props.name).equalTo(‘Davin Ryan’);
expect(props.height).equalTo(‘183cm’);
expect(props.interests).equalTo(‘Javascript, React and Recompose’);
expect(props.gender).equalTo(‘male’);
});
});
Summary
Ultimately we are using less code that looks like code and writing more code that expresses what we are trying to achieve i.e. Declarative programming.
In our example re-written with Recompose you’ll notice that we didn’t write a lot less code. It’s almost the same but we used more HOFs and HOCs which made our code more declarative/expressive.
How HOCs make you a better programmer:
- HOCs are very expressive which means it’s easier to understand what’s going on without staring for ages at code
- HOCs, especially when chained (for composition), allow us to build some pretty powerful components very quickly
- Recompose is a library of other HOFs and HOCs that we can use to make more HOCs. It’s like a batman utility belt for building HOCs
- If you find yourself writing a lot of code in different places that does the same thing, you may be able to refactor that code into a reusable HOC
Exercises
Improve your understanding of Higher Order Components by writing on that takes a
- component and sets default properties
- component and adds scroll behaviour
- component and makes it responsive by passing a isSmall or isLarge Property using MatchMedia
- list of child components and sorts them
Learning Materials
- Code used in this post can be found in this code sandbox
- Code used in this post can be found at Github here
- Excellent guide to designing HOCs with React with really nice standards
- Recompose Docs
Source: https://www.davinryan.com/get-started-with-higher-order-components-and-recompose/