You have probably used Higher-Order functions in your JavaScript apps at one point or the other and the power and functionality it gives to developers fascinate you. But do you know you can achieve similar satisfaction using React? This is where React’s Higher-Order component comes to play!
Table of Contents
- Prerequisites
- Introduction to Higher-Order Components
-
Recap of Higher-Order functions
.map()
.forEach()
.filter()
- Deep dive into Higher-Order Components
- Using a Higher-Order Component
- Other Use Cases
- Conclusion
Prerequisites
This is an advance concept in React. To follow along you will need the following:
- Fundamental knowledge of JavaScript
- Fundamental knowledge of React
- NPM installed on your computer
- The latest version of React installed on your computer, and
- The grit to complete the tutorial :-)
Introduction to Higher-Order Components
The concept of a Higher-Order component (HOC) in React is an advanced technique that allows the reuse of a components’ logic by other components. It enables software developers to factor out common patterns or functionalities among various components or a group of components and creates a single component that you can then reuse in place of all the components having common structures or properties.
A typical React application comprises of various components and sometimes these components might have some similarities. Because of the hassle of having to copy and paste similar components next to each other just to change that little detail between them, the React team came up to use “Higher-Order components”.
HOCs allow software developers to write cleaner and more maintainable code by not repeating the same piece of code repeatedly. This abides by the principle of don’t-repeat-yourself (DRY) which is a core principle to follow in every software development.
HOCs in React are built upon the foundation Higher-Order functions. So before we go deep into the details of HOCs, let’s do a quick recap of Higher-Order functions.
Recap of Higher-Order Functions
A Higher-Order function in JavaScript is a function that takes in functions as parameters and/or returns a function as an output.
Below are some examples of the built-in Higher-Order functions built into JavaScript.
-
.map()
: When called on an array, the method executes a callback function on each element of the array. The callback function takes in each array element one at a time, performs an operation with it, and then returns the result. It then stores the result in a new array which the method then returns.
Please note that this method does not modify the original array it is called upon, but it returns a new array whose elements might differ from the original array it was called upon.
The example below shows the .map()
method in action.
const nums = [1, 2, 3, 4];
const squares = nums.map((num) => num * num)
console.log(nums) // [1, 2, 3, 4]
console.log(squares) // [1, 4, 9, 16]
The first log statement shows that the original array is not modified. The second log statement shows how the method returns a new array whose element is determined by the callback function.
Note: A callback function is a function that is passed into another function as an argument, usually Higher-Order functions. This callback function can then be executed inside the Higher-Order function it was passed in.
-
.forEach()
: This method also takes in a callback function as an argument and passes each element of the array it was called upon into the callback function one at a time. The array element passed into the callback function is then used inside it to operate.
Unlike the .map()
method, this method returns undefined
. It does not modify the array elements it was called upon as well.
The example below shows the .forEach()
method in action.
const even = [2, 4, 6, 8];
const array = even.forEach((num) => {
console.log(num + 1);
})
console.log(array)
// Output
// 3
// 5
// 7
// 9
// undefined
The code example increments each number by 1 and logs the output. The last line (undefined
) shows the return value of the .forEach()
method.
-
.filter()
: The method behaves similar to the two methods mentioned above, i.e. it takes a callback function that works on each element of the original array it was called upon. The only difference is that it returns an array that is a subset of the original array.
As the name applies, this method filters an array to return a new array. It does not modify the elements of the original array.
The elements that get added from the original array to this new array are dependent on whether the callback function returns true
or false
.
Below is an example of using the .filter()
method:
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const even = numbers.filter((num) => num % 2 === 0)
console.log(numbers) // [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
console.log(even) //[ 2, 4, 6, 8, 10 ]
The first log statement above shows that this method does not modify the original array, and the second array shows the returned array that was filtered (even numbers) from the original array.
The above methods are just a few examples and uses of Higher-Order functions. There are a bunch of Higher-Order functions in JavaScript that you can utilize in your code, and the best thing is that you can also create your own.
Now that we have had a quick recap of Higher-Order functions, it’s a good time to continue with the discussion on Higher-Order Components.
Deep dive into Higher-Order Components
In simple terms, a Higher-Order component is a function that takes in a component and returns a new component, which can then be stored as a component. The new component returned wraps over and does not modify the previous input component.
HOCs were inspired by Higher-Order functions, which is the main reason we had to go over a quick recap of what Higher-Order functions are.
Below is a pattern that most HOCs follow:
const higherOrderComponent = InputComponent => {
class Wrapper extends React.Component {
render() {
return <InputComponent />
}
}
return Wrapper
}
Below are a few things you need to know about HOCs
They are functions
This might sound conflicting, but let us look at it closely. When a HOC takes in a component, it returns a new component, which is then stored in a variable. This variable is then referred to as a component because it contains a component returned from the function.They take in one or more components as an argument
In most cases, they also take in another parameter along with these components. This parameter is then used to determine the output of the new component. The parameter can either be a callback function, a data object, a variable, or whatever you chose.They return a new component
This new component that is returned is the input component wrapped with more functionality or design.They are pure functions
HOCs have no effects on the component they take in. They don’t change the behavior of the input components.
Using a Higher-Order Component
Having discussed a bit of the story about HOCs, it’s now time we get into the application. The examples below show where you can use HOCs in your everyday apps. From the examples, you will get to know more about HOCs and why you need to use them in your React applications.
For this example, we will create a simple prototype of a blog site that contains social media links, navigation links, and the actual blog content of the page. Each page should contain navigation links and the blog’s social media handles.
If you are new to React, ensure to go through the note below, else you can simply just jump through.
Note: We will use
create-react-app
to bootstrap our project. Before starting on the project, follow these steps below:
- Navigate to your desired directory and run the command
npx create-react-app my-app
. This will create a my-app directory, which is your project directory containing all you need to spin up a react app in your browser.- Navigate to the
src
folder and delete all its content exceptindex.js
andApp.js
. We are building a very simple app and we won’t need the other files.
Let’s start by creating a sample blog page.
Once you are inside the src
folder in the project directory, create a file named page1.js
and add the following contents to the file:
const Page1 = () => {
return (
<div>
<h1>Page 1</h1>
<p>Page 1 contents here</p>
</div>
)
}
export default Page1
The code above creates a simple functional component that returns some content. This component is then exported so it can be imported into other components.
We will create three pages for this tutorial to simulate a website with one blog post per page. The second and third pages will contain similar contents to page1.js
we just created. You can create both of them as page2.js
and page3.js
. Then add the contents of page1.js
we just created above to the two pages you just created. The last step is to change every occurrence of “Page 1” to “Page 2” and “Page 3” in both files, respectively.
After creating the pages, the next step is to create a wrapper we will use to wrap each page. This is because we want each page of the blog to have a link to the blog’s social media accounts and the navigation bar.
Inside the src
directory, create a file named Wrapper.js
and the following contents into the newly created file:
const Wrapper = (Page) => {
const PageWrapper = () => {
return (
<div style={{height: “100vh”}}>
<div>
Connect with us on
<a href=“” > Facebook </a>
<a href=“”>Instagram </a>
<a href=“”>Twitter</a>
</div>
<hr />
<p>Home</p>
<p>About</p>
<p>Contact</p>
<hr />
// Input component displays here
<Page />
</div>
);
};
return PageWrapper;
};
export default Wrapper;
We created a functional component named Wrapper
which takes in another component named Page
. Inside of the Wrapper
component is another component named PageWrapper
that is used to wrap around the Page
component. The PageWrapper
contains other contents we would like to be displayed on every page on the blog, and then the <Page />
component, which contains contents specific to that page alone.
Note: We added a little styling to the wrapper component to make each page occupy the entire height of the screen. This is just to simulate the behavior of a typical blog page.
Towards the end of the file, the PageWrapper
component is returned to the Wrapper
component. The return statement implies that when we call the Wrapper
component with an input component (Page
in this case) we get the PageWrapper
component in return.
The last line of the file is exporting the component so it can be used outside of the file.
Having created a wrapper that can wrap all the pages, the next step is to use the wrapper on each of the pages we created. We can achieve this by importing the wrapper into each of the pages like this:
import Wrapper from ‘./Wrapper’
And then replace the export statement at the bottom of each Page component with this line (please edit the Wrapper
argument as appropriate i.e to match Page2
and Page3
components respectively):
export default Wrapper(Page1)
What we just did up there was taking the whole content of each page and wrapping it with the wrapper before exporting it. Recall that Wrapper
takes in a component, so we must pass in this input component wherever Wrapper
is called.
Now that we are done creating the files, the next step is to import these components into our App.js
. Replace the content of your App.js
file with the following code:
import React from “react”;
import WrappedPage1 from “./Page1”;
import WrappedPage2 from “./Page2”;
import WrappedPage3 from “./Page3”;
const App = () => {
return (
<div>
<WrappedPage1 />
<WrappedPage2 />
<WrappedPage3 />
</div>
);
};
export default App;
The first set of import statements imports the contents of our blog pages with a suitable name that can refer to these imported pages.
The next block of code is a functional component named App
which returns a div
. Inside of this div
is where we include the wrapped components we imported earlier. You might notice that we are using the tag <
and />
to display these imported components. This is because the Wrapped
component we created earlier returns another component. So for React to treat it as a component, we must include the tag.
At this point, if you navigate to the project directory inside your terminal and run the command
npm start
It will open a new window in your browser, which will look similar to the one below:
And if you scroll down to the next page, you will notice the navigation and the social media links are still present at the top of the page. This is the same for the third page as well.
So from the example above, you notice that because we are using Higher-Order components; we didn’t have to repeat similar patterns between pages. We simply create a component that has all these similarities and wraps all other components with it.
The steps we went through above might seem stressful just to achieve something as little as that. But imagine if you have over 3 pages, say 5, or 10, or even 50 pages, it will become hard and eventually become impossible to copy and paste the social media account and navigation links into all the pages.
Another benefit we derived from using HOC for the above example is that it made our code simple and easy to debug. If there is any change that needs to be done on any of the social media links, we can make the change only in the wrapper component and it will reflect everywhere.
Other Use Cases
The example we illustrated above is just one out of the many areas where you can apply HOCs in your React apps. Below are some other areas where they can be used:
- Controlled display: You can use a HOC to control the components that will be displayed to the currently logged-in user based on their role. For instance, users who are not logged in will only be able to see the Login component. Logged in users will see the page content but cannot see all users of the app, nor those online. But the admin can see all of these.
This might seem like something that can be achieved using conditional rendering. But imagine an app where tens or even hundreds of components on a single page where the currently logged-in user can be multiple roles from a bunch of roles. Having such logic in the main app component is not the ideal design. In some situations, the main app component might also have some logic running within it, so combining that with the logic of conditional rendering will be cumbersome and hard to maintain.
Automatically pass properties into components: You might have a bunch of properties you want to pass into several components. This process might seem repetitive. You can easily achieve this using a HOC to wrapping each component. The HOC then passes these props into every component it takes in.
Display loading page: You might also want to display a loading page for the user while a data fetch from an external source is still loading. And then display the page contents when the data is done loading. Both the loading page and the content page will be wrapped inside a HOC, which will then return one component at a time.
This is a good design practice because it abstracts that little functional logic in our code from the main logic.
The above examples are a few of the use cases of HOCs. There are many more out there that are easy to detect. Compare them with the examples we have discussed here and you will not have a hard time deciding whether to use a HOC in your next React app or not.
Conclusion
The aim of this tutorial is to enlighten the readers on what HOCs are and how they can use them in their everyday React applications. In this tutorial, you just learned the basics of Higher-Order functions, what HOCs really are, and the application of HOCs.
The concept of Higher-Order components might be difficult for beginners to follow along. It is advisable go grasp the basic of JavaScript and Higher-Order functions before coming back to the tutorial.
With Higher-Order components, we are able to factor out common fragments in our code and reuse them across these components. We have also use HOCs to filter what components to display, as well as reducing the logic of conditional rendering on the main component. This made our code easy to debug and new developers can easily follow along. If you are interested in learning more about React HOCs, do well to checkout the React documentation where they dive deeper into using HOCs. I hope you start using HOCs in you apps as soon as you grasp the concept.
Happy coding!
Top comments (0)