HOC is Still Useful And Applicable in React

Sep 25, 2021
5 min read

Higher-order components allow you to keep your React application well-structured and maintainable. We're going to focus on how pure function keeps your code maintainable and clean and how we can apply the same principles to React components.

Pure Functions

In a pure function, you will get all these properties that I am mentioning below.

Whenever a pure function deals with data, it is declared as an argument.
When data is passed to a pure function, it does not mutate it.
If you give the same input to the pure function, it will always return the same output.

Here is an example:

function sum(x, y) {

return x + y;

}

Now I will give you an example of an impure function:

let y = 2;

function fAdd(x) {

return x + y;

}

See that this function is not pure as it is referencing data that is not passed to it directly. That is, you can take different outputs from the same input.

let y =  2;

fAdd(3) // 5

y = 3;

fAdd(3) // 6

One of the biggest benefits you can get from the pure function is that it makes testing or debugging any application easier. Occasionally it might happen that you also make impure functions that have side effects or you modify the behaviour of an existing function, which you were not getting direct access to.

We need to look at higher-order functions now to modify the behaviour and get access.

Higher-order Functions

A function that returns a function when called is called a higher-order function. To a higher-order function, the function is often passed as an argument. But it doesn't matter whether the argument is passed or not, for any function to be higher order.

We create a function sumAndLog in which we log the output to the console before returning it.

function sumAndLog(x, y) {

const output = sum(x, y);

console.log(`Result: ${result}`);

return output ;

}

If logging is important to us, we need to do the same for the subtraction function as well.

This is where our higher-order function will come in handy and we don't have to duplicate the sumAndLog code. Higher-order functions give us an easier path. A higher-order function takes a function and returns a new function. It calls the given function, logs the output and finally returns it.

function loggingAndReturn(func) {

return function(...args) {

const output = func(...args)

console.log(Output, output);

return output;

}

}

Here, a higher-order function is accepting a function as an argument. And the same higher-order function is also returning a new function.

const sumAndLog = loggingAndReturn(sum);

addAndLog(4, 4) // 8 is returned, “Output 8” is logged

const subtractAndLog = loggingAndReturn(subtract);

subtractAndLog(4, 3) // 1 is returned, “Output 1” is logged;

Now any function we pass, whether it is "add" or "subtract", the log will be added to both the functions.

loggingAndReturn has become a higher-order function. The advantage of a higher order function is that if there is an existing function whose behavior is not to be changed, HOC will wrap it up and provide your solution.

Now we come to React.

We also use the same logic in React Components. That is, we can give additional behaviour to React components.

It is also important to note that after hooks were introduced, the use of HOCs was slightly reduced. Because you can do behaviour sharing with hooks and for which no extra components are needed. Despite all this, the HOC proves to be a useful tool during development.

Now let us discuss the React Router. I would suggest that you make sure to take a look at the React Router documentation.

React Router has a <NavLink> component that links pages together. Whenever the user is on a URL via <NavLink>, <NavLink> has a property named activeClassName that gives style to the URL.

<NavLink to="/contact" activeClassName="active-link">Contact</NavLink>

Now I'm going to create a component that will wrap this <NavLink> component.

Now look, I can write the same line which seemed verbose before, like this:

This is how a component is made a higher-order component in React, That is, by manipulating an existing component and not changing it, a new wrapper component is created, which is called a higher-order component.

Apart from this, <NavLink> has many other properties. And they will also be needed while working, due to which we have to make the component more extensible.

The syntax JSX used in React supports the spread operator. With the spread operator, you can pass the object to the component as properties.

const props = { c: 6, d: 7};

<Foo a={props.c} b={props.d} />
<Foo {...props} />

Here {...props} is the individual property of Foo and this property is the object within which the key is spread.

By taking advantage of this trick, we can access any property of <NavLink>.

And an even bigger advantage is that our wrapper component will already be supported if a new <NavLink> property comes along in the future.

Sometimes we want similar behaviour across multiple components. I already talked about inserting the same behaviour above when I log the output to the console.

Think of it this way that an application has an object that holds the current user's information and the user has got authentication on the system.

Now you want some other React components to have access to this information as well. Here I would like to reiterate that not all components, just some, we need to be strict about who will access the information.

For this, we will create a function to which a React component will be passed.

This function is returning you a new React component. This new component will be rendered with a new property that will have access to user information. Look at the above example carefully. In this, the component is getting an additional property named importantUserInfo.

The <AppMyHeader> component will need this information in order to show the logged-in user.

const AppMyHeader= function(props) {

if (props.user) {

return <p>You are logged in as {props.user.name}</p>;

} else {

return <p>Login</p>;

}

}

You can access the user object by rendering <connectedMyAppHeader>.<connectedAppHeader> has got additional data that not every component will have.

const ConnectedMyAppHeader = wrapWithUser(AppMyHeader);

You can access the user object by rendering <connectedMyAppHeader>.<connectedAppHeader> has got additional data that not every component will have.

This pattern is very common in React libraries and especially when it comes to Redux, there is much more to it.

Conclusion

It feels good to conclude this article now because with this technique we can create a great codebase that will be very easy to maintain and creates peace for us on a daily basis.

You can define the data of higher-order components in one place. The refactoring is so good that your data remains private and only data is exposed that the components really need.

As the application grows, it will be beneficial for you to understand which components are using what kind of data.