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