Have you ever used React and felt compelled to duplicate the same logic across numerous components? I'm sure you're wondering if there's a method to reuse logic across multiple components in React without rewriting it. Yes, there is! Higher-Order Components (HOCs). HOCs offer an advanced approach to dealing with such cross-cutting concerns.
In this tutorial, you'll explore HOCs in-depth, why there’s a need for HOCs, when to use the pattern, what's a HOC and finally, create a simple React app using HOCs.
Why’s there a need for a HOC?
Imagine a client approached you, then asked you to create an App with a button indicating the number of times it’s clicked. You create a component called ClickCounter
, where you store the button
and its counter functionality. Export, then import and render it in the App
component.
After some time, the client comes back and asks you to add a heading indicating the number of times it’s hovered over. Basically a ClickCounter
with click functionality replaced by hover functionality. You create a new component called HoverCounter
which stores the heading and counter functionality. Export, then import and render it in the App
component under ClickCounter
.
Again, the client comes back and asks you to add an input that counts the number of keypresses in the project. For Example A key up in an input element to increment a counter value and display it. You can of course implement it the same way you implemented the counter in ClickCounter
and HoverCounter
. But realize you are duplicating code and not really reusing the counter functionality. So if 10 different components needed the counter functionality you would write the same code over and over again.
So how can you really reuse the counter functionality?
The immediate thought is to lift state
from the parent component and pass it down the handler as a prop
. Define the counter functionality in App
component, provide the state and the handler as props to ClickCounter
and HoverCounter
. Since you only have two components, it would work. But imagine a scenario where the counter components are scattered across the React App. Lifting the state
would definitely not be the correct solution. So there’s a need to share common functionality among components without repeating code. That’s where the concept of HOCs comes in.
So when do you really use the HOC pattern?
Basically HOCs allow you to share common functionality between components.
What’s a HOC?
Basically, a HOC is a pattern where a function takes a component as an argument and returns a new component.
A HOC adds additional data or functionality to the component. The new component can be referred to an EnhancedComponent
.
From a non-technical point of view
withSuit
is the function that will enhance TonyStark
and return IronMan
which is the EnhancedComponent
.
From React's point of view we have function which accepts OriginalComponent
adds functionality and returns the EnhancedComponent
.
Consider implementing a basic HOC then implement it with the counter example-
Create a new file called withCounter.js
within the components folder.
Consider this example-
In the above example, the UpdatedComponent
is a function that takes a component called OriginalComponent
as an argument. You have created a new component called NewComponent
which returns the OriginalComponent
with a name prop
from its render function. While this actually adds no functionality, it depicts the common pattern that every HOC will follow.
Creating a simple React app using HOCs.
Apply the HOC pattern to the ClickCounter
and HoverCounter
. In ClickCounter
you will import UpdatedComponent
from withCounter
while exporting it will call the UpdatedComponent
function passing in the ClickCounter
component. You’ll do the same for HoverCounter
. Instead of exporting the ClickCounter
or HoverCounter
component you export the UpdatedComponent
. The HOC in addition to being the ClickCounter
or HoverCounter
.
To modify your HOC so as to allow the counter functionality to be shared among components. You will cut the constructor
and incrementCount
function from ClickCounter
into the HOC. Since it’s the common functionality we want to share, remove the same code from HoverCounter
.
You need to pass down the state
and incrementCount
method as props
so that the OriginalComponent
can make use of that functionality.
In both ClickCounter
and HoverCounter
you will destructure count
and incrementCount
from this.props
. If you look at the project, it functions as perfectly but the difference now is that you are reusing code instead of duplicating it.
Now, you can change the naming convention. The component and functions you have used are different from what you typically see. So change them. The function and file name is usually the same, it indicates the functionality being added to the components. The OriginalComponent
is usually referred to as the WrappedComponent
. The NewComponent
is usually the same as the function name, but in Pascal case
.
You’ll make the same changes to the naming convention while importing and exporting the HOC in the ClickCounter
and HoverCounter
.
Before winding up consider these two things:
- Passing down the
props
. InApp
component you will pass a nameprop
in theClickCounter
component.
Render the name prop
in ClickCounter
.
If you go to the browser the name value is not displayed. This is a common mistake when starting off with HOCs. The problem here is that when you specified props
on the ClickCounter
component, the props are passed down to the HOC and not ClickCounter
. If you log out the name from in the HOC in the render
method. You can see you do have the name prop
for ClickCounter
and undefined for HoverCounter
.
To fix this issue you need to pass down the remaining props
to the WrappedComponent
using the spread operator. Basically the HOC adds two props
to the App
component then passes down the remaining specified props
.
- Passing parameters to the HOC function.
In the HOC instead of incrementing the value by 1. You would want to increment it by different numbers for both
Counter
components. You can do that by passing a parameter to the HOC function. FirstWrappedComponent
and secondincrementNumber
. You will now increment theincrementCount
by theincrementNumber
parameter.
In ClickCounter
you will add a second argument 5 and HoverCounter
You will add 10.
Conclusion
Congratulations! 🚀 You’ve reused common functionality within components in your application. Following the methodology in this tutorial can reduce errors caused by duplicating code. I hope you enjoyed this tutorial!
Top comments (1)
I really like the const
IronMan = withSuit(TonyStark);
analogy you made