Image by pieonane from Pixabay
Table of contents
- Higher Order Functions
- Higher Order Components in React
- Things to take note of while using HOC
- Conclusion
- References
In this brief article, I will introduce you to Higher Order Components
in react, sometimes written as HOC
in brief.
Higher Order Function
For us to understand HOC in react, it is important to understand Higher Order Functions in javascript. According to The eloquent javascript, functions that operate on other functions, either by taking them as arguments or by returning them, are called higher-order functions. In javascript, functions are first class citizens. This means a function can be passed as argument to another function, it can be returned from another function and can also be assigned to a variable. In javascript, there a number of builtin higher order functions. The most common ones are the Array
methods below:
map
forEach
filter
reduce
In the example below, I have implemented a function which is used for raising a number to a given power. It is a higher order function because it returns a function.
const power = (power) => {
return (base) => {
return base ** power;
};
};
const square = power(2);
const cubed = power(3);
console.log(square(100)); // 10000
console.log(cubed(100)); // 1000000
Essentially, higher order functions are regular functions that do one or both of the following.
- Takes one or more functions as arguments
- Returns a function
Higher Order Components(HOC) in React
A higher-order component, in react, is a function which takes a component as an argument and returns another component. According to the react documentation,
A higher-order component returns a new component after performing some kind of operation. For example,
const EnhancedComponent = higherOrderComponent(WrappedComponent);
Essentially a HOC transforms the input component into another component.
From the simple definition of a Higher Order Component above, you can immediately notice the similarities between Higher Order Functions such as Array.prototype.map
, Array.prototype.filter
, Array.prototype.forEach
et cetera and Higher Order Components in React.
To illustrate what I am talking about, let me give a contrived example below. It is unlikely you will do this in a real app but it goes a long way in explaining what HOC is.
import React from "react";
function HigherOrderComponent(Component) {
return class extends React.Component {
render() {
return <Component {...this.props} />;
}
};
}
function SayHello(props) {
return <p> Hello {props.name} </p>;
}
export default HigherOrderComponent(SayHello);
From the above, you can see that HigherOrderComponent
takes Component
as parameter. It then returns anonymous class component which renders the component passed as argument to HigherOrderComponent
. It is worth pointing out that I have used an anonymous component but you can also name it to increase readability of your code. A component which has been named appropriately can give you an idea of what it does even without looking at its code.
The above component is a HOC but it is such a contrived example that you are unlikely going to use or encounter it in real-world projects. One of the main reasons for using HOC is to add enhancements to the component with functions or data, none of which our component above implements.
One use case of a HOC is to display a loading indicator while fetching data from an API. Below, I have defined a HOC which displays the text Fetching Photos...
if this.props.isFetchingPhotos
is true
otherwise it returns a new component which renders the component passed as argument to the HOC. Notice how the props
have been passed on to Component
from Component {...this.props}
.
withLoadingIndicator.js
import React from "react";
function withLoadingIndicator(Component) {
class WithLoadingIndicator extends React.Component {
render() {
if (this.props.isFetchingPhotos === true) {
return <p> Fetching Photos... </p>;
}
return <Component {...this.props} />;
}
}
WithLoadingIndicator.displayName = `WithLoadingIndicator(${Component.displayName ||
Component.name ||
"Component"})`;
return WithLoadingIndicator;
}
export default withLoadingIndicator;
The above HOC is imported in App.js
. It is then invoked with Photos
as argument. The returned component is then finally rendered inside App.js
.
import React, { useEffect, useState } from "react";
import Photos from "./Photos";
import withLoadingIndicator from "./withLoadingIndicator";
import "./style.css";
const url = "https://jsonplaceholder.typicode.com/photos";
const WithLoadingIndicator = withLoadingIndicator(Photos);
export default function App() {
const [photos, setPhotos] = useState([]);
const [isFetchingPhotos, setIsFetchingPhotos] = useState(false);
const [hasError, setHaserror] = useState(false);
useEffect(() => {
async function fetchPhotos() {
setIsFetchingPhotos(true);
try {
const response = await fetch(url);
const photos = await response.json();
if (Object.keys(photos).length === 0) {
throw new Error("Something wrong has happend");
}
setPhotos(photos);
} catch (error) {
setHaserror(true);
}
setIsFetchingPhotos(false);
}
fetchPhotos();
}, []);
if (hasError === true) {
return <p> An Error has occurred... </p>;
}
return (
<div>
<WithLoadingIndicator photos={photos} isFetchingPhotos={isFetchingPhotos} />
</div>
);
}
If you have ever used state management library redux, most likely you have come across react-redux which is used for connecting your app to the redux store. The connect
function returns another function which is a HOC. The component you want to connect to the redux store is passed to the HOC returned by a call to connect
.
const higherOrderComponent = connect(mapStateToProps, mapDispatchToProps);
const EnhancedComponent = higherOrderComponent(Component);
Things to take note of while using HOC
Before you start using Higher Order Components, there are certain things you must always be cognizant of. These include:
Higher Order Components must be pure.
You shouldn't perform side effects in HOC and it shouldn't mutate the component passed to it. It should instead return a new component which renders the component passed as argument. A HOC is supposed to compose a component for code reuse. If you are writing a HOC, avoid the temptation to mutate the component passed as argument. This is because once mutated, the input argument's new behaviour will also be reflected outside the enhanced component which makes it non reusable. Instead of mutation, HOCs should use composition, by wrapping the input component in a container component. It is expected HOCs pass on props that are unrelated to its specific concern.
Naming convention for HOC
It is important to choose a display name that communicates to whoever is reading/debugging your code that the rendered component is a result of a call to a HOC. For example if your higher order component is withLoadingIndicator
and input component is Component
from the example I provided above, then the returned component can use the display name withLoadingIndicator(Component)
. This will make it obvious that higherOrderComponent(Component)
is a result of passing Component
to
withLoadingIndicator
. This is illustrated in the example above.
Don't invoke HOC inside the render method.
In class components, the render method is invoked whenever a component renders. Invoking the HOC inside the render method like in the illustration below means every time the component is rendered, HOC is invoked. This will affect your app's performance. The best place to invoke the HOC is outside the component. If you are interested in creating a HOC dynamically, make use of the life cycle methods.
render(){
const EnhancedComponent = HOC(Component);
return <EnhancedComponent />;
};
According to the react documentation,
React uses component identity to determine whether it should update the existing subtree or throw it away and mount a new one. If the component returned from render is identical (===) to the component from the previous render, react recursively updates the subtree by diffing it with the new one. If they are not equal, the previous subtree is unmounted completely.
Since invoking the HOC inside the render method will lead to the creation of a new component on each render, this leads to the unmounting and remounting of the component which makes the component and all its descendants lose their state.
Static methods must be copied over
Sometimes it is important to declare static methods in class components. If you wrap a component on which a static method has been defined inside a HOC, the enhanced component doesn't have access to the static method. Since this is a brief introduction to HOC I won't describe how to go about solving this problem in this article,the react documentation prescribes solutions which you can read about here.
refs
are not passed
It is common practice to pass props
which have been passed to the enhanced component to the wrapped component. However, this is not possible with refs
because refs
are not handled by React like ordinary props. The solution is to use forwardRef
API. If you are not familiar with it, you can read about forwardRef
in one of my earlier articles What is forwardingRef
and how do you use it?.
Conclusion
In this brief article about HOC, we learned about:
- Higher Order Functions and how it is related to Higher Order Components.
- Higher Order Components. What HOCs are, when it is necessary to use HOCs and things worth taking note of while using HOCs.
I am glad you have read this article till the end. If you find anything technically inaccurate, feel free to leave a comment below. If you find it useful, consider sharing it on social media or any other platform. Others might find it useful too.
Top comments (0)