DEV Community

Sivantha Paranavithana
Sivantha Paranavithana

Posted on • Updated on

HOCs in React

In the previous post we discussed what is stateful logic sharing and why do we need it.

In this article let's discuss about HOCs which is one of the popular solution for the stateful logic sharing.

According to the React documentation, a higher-order component is a function that takes a component and returns a new component.

In other words, a higher order component is a function that transform a component into another enhanced component.

const EnhancedComponent = higherOrderComponent(WrappedComponent);
Enter fullscreen mode Exit fullscreen mode

HOCs are used in common React libraries such as Redux’s connect and Relay’s createFragmentContainer.

Now let's see how HOCs can help us in avoiding duplication and stateful logic sharing.

In the previous article we saw that in each counter component, there were sharable stateful logic. We can move that into a HOC and wrap our counters from that HOC.

Let's see that in code.

First, we create a function called withCounter which takes a component as an argument. This function will return a new React component. And we can move all the sharable stateful logic inside of that new component.

Then we can pass the count state and the increment handler as props to the wrapped component.

Notice that we are also passing any additional props that might get passed into the new component we are creating, to the original component through {...this.props} notation. In that way we can still pass props to the original component even after wrapping it by the HOC.

Since we are now passing the count state and the increment handler as props, we have to reflect that change in each component as below, where we get the count and the increment handler from props.

import React, { Component } from "react";

export class ButtonCounter extends Component {
  render() {
    const { count, increment } = this.props;
    return <button onClick={increment}>Clicked {count} times!</button>;
  }
}

export default ButtonCounter;

Enter fullscreen mode Exit fullscreen mode

With that, we can create the components with the counter functionality without duplicating the code using the HOC as below.

const ButtonWithCounter = withCounter(ButtonCounter);
const HoverWithCounter = withCounter(HoverCounter);
const InputWithCounter = withCounter(InputCounter);
Enter fullscreen mode Exit fullscreen mode

With that, we don't need to lift the state up to share the state and the logic, therefore we can reuse this stateful logic in anywhere in the react component tree.

Not all HOCs look the same. Sometimes they accept only the component we want to wrap as the argument. Eg: withRouter HOC in React Router.

const NavbarWithRouter = withRouter(Navbar);
Enter fullscreen mode Exit fullscreen mode

Some HOCs accept additional arguments. Usually these arguments are used inside the HOCs for configurations of the HOC. eg: createContainer in Relay.

const CommentWithRelay = createContainer(Comment, config);
Enter fullscreen mode Exit fullscreen mode

Some HOCs look like below.

const ConnectedMyComponent =  connect(mapStateToProps, mapDispatchToProps)(MyComponent);
Enter fullscreen mode Exit fullscreen mode

This pattern is common in React Redux. connect is just a function that returns an HOC. Then that HOC is used to wrap the MyComponent. We can see it clearly when we break it down as below.

const enhance = connect(mapStateToProps, mapDispatchToProps);
const ConnectedMyComponent = enhance(MyComponent);
Enter fullscreen mode Exit fullscreen mode

If we want to talk more about HOCs, there are few things to keep in mind when creating HOCs.

  • We should avoid mutating the original component inside the HOC at all costs.

The reason for this is, when you mutate the original component's prototype inside the HOC, it affects every instance of the original component. This makes the original component unreliable to use anywhere else.

For an example, let's say we created an HOC which mutate the prototype of a component, and wrapped a component named MyComponent with that HOC. Then even when we use MyComponent without wrapping it with the HOC, it will still contain the mutation that the HOC did. Furthermore, if we apply another HOC on top of that, it might replace the first HOCs logic accidentally as well. Therefore, making HOCs as pure functions is crucial.

  • Don't use HOCs inside the render method.

Each time render method returns a component, React compares the previous component subtree recursively with the new subtree to identify any changes and decide either to update the component subtree or to unmount the current subtree completely and render the new subtree as a new.

And when using HOCs to wrap components, each of these components receive separate states. If we were apply HOC to a component inside a render method, each time the component get unmounted, the state of that component and all of children to be lost.

Instead of applying inside the render method, apply HOCs outside the component definition so that the resulting component is created only once. Then, its identity will be consistent across renders.

  • Static methods must be copied over.

Static methods inside a component won't be available in the new component which wrapped the original component with an HOC. Therefore we must copy all the static methods to the new component before returning it.

  • Refs aren't passed through

Although we pass refs as props to components, it's really not a prop. It is handled specially by React just like the key prop. Therefore refs won't be passed to the original component from the wrapped component through {...this.props} notation. Therefore, instead of using refs we should use forwardRefs and that is discussion for another time😉.

Now that we talked about HOCs, let's talk about render props which we can use as another solution for the stateful logic sharing in the next article.

Discussion (0)