High Order Components in React

Published on
5 min read
High Order Components in React

I have been abusing the usage of High Order Components(HOC) in my last two projects. Its a great way to enhance your components and also its a nice workflow to have. You can combine multiple HOCs together to build a powerful feature. By doing so, you can have a nice separation of concern.

To make our development faster, we use libraries which provide various functionalities as a HOC. Redux is a great example for this. It connects the global store with our application, and provides us with special methods like dispatch to make changes to the store...

Concerns:

A high order component is a function which takes a component, enhances it and returns us a new component which may contain additional helper functions or a new behaviour. Since this composition happens behind the scenes, it can be risky at times and might be difficult to troubleshoot when something goes wrong.

 

Unsplash - Anonymous
Unsplash - Anonymous

 

Lets look at the below code on how HOC works.

import { withPostData } from './hoc/withPostData';

function Post({ data, loading }) {
  if(loading) return <Loader/>;
  return (
    <div>
      <div>{data.title}</div>
      <div>{data.html}</div>
      <div>{data.author}</div>
    </div>
  );
}
export default withPostData(Post)

In the above example we wrap a Post component with withPostData hoc and this gives us two additional property data and loading. Now our component only have to take care of rendering this data. It is our hoc's responsibility to try different mechanism of fetching the data, either from localStorage, some different endpoint, etc. Our withPostData hoc looks something like this.

const withPostData = WrappedComponent => props => { 
  const [data, setData] = useState({});
  const [loading, setLoading] = useState(true);
  const postUrl = getSlugFromUrl(document.location);

  useEffect(() => {
    const result = await axios(postUrl);
    setData(result.data);
    setLoading(false);
  },[postUrl]);

  return (
    <WrappedComponent
      {...props}
      loading={loading} // additional property
      data={data}       // additional property
    />
  );
};

This works great. We correctly get the loading state and render the data when the loading has finished.

Now we have a new requirement. We would like to display some additional meta data like tags just after the title of the post. So we can again wrap our component with another hoc. Since hocs can be reused, its a good idea to keep them separate. Lets call this hoc as withPostMeta.

const withPostMeta = WrappedComponent => props => { 
  const [data, setData] = useState({});
  const [loading, setLoading] = useState(true);
  const postUrl = getSlugFromUrl(document.location);

  useEffect(() => {
    const result = await axios(postUrl);
    setData(result.data);
    setLoading(false);
  },[postUrl]);

  return (
    <WrappedComponent
      {...props}
      loading={loading} // additional property
      metaData={data}   // additional property
    />
  );
};

This hoc is going to add two properties metaData loading. So now our code would look like this.

import { withPostData, withMetaData } from './hoc'

//  a problem here
function Post({ metaData, data, loading }) {
  if(loading) return <Loader/>;
  return (
    <div>
      <div>{data.title}</div>
      <div>{data.html}</div>
      <div>{data.author}</div>
    </div>
  );
}
export default withMetaData(withPostData(Post));

But now we have a problem. Both the HOC's provides additional properties but one has the same name (loading) in both the HOC's. So depending on the order of how you compose the hoc, one will override the other hoc's property. In this case Post will always recieve the loading state from the withPostData and it will never know if the loading state of withMetaData

If your using external libraries, we have no control on what happens behind the scenes. There is a possibility that one can override the properties of other Hocs. The only way to avoid this is to be mindful while developing libraries or writing your own.

How can we fix this ?

 

Unsplash - Anonymous
Unsplash - Anonymous

 

You can follow how redux does it with mapDispatchToProps. You may develop hoc which accepts two arguments, the component and a function propsMapper which is going to map the hoc data with the keys that you provide. So with that, our withPostData HOC will look like this.

const withPostData = (WrappedComponent, propsMapper) => props => { 
  const [data, setData] = useState({});
  const [loading, setLoading] = useState(true);
  const postUrl = getSlugFromUrl(document.location);

  useEffect(() => {
    const result = await axios(postUrl);
    setData(result.data);
    setLoading(false);
  },[postUrl]);

  // does the mapping here
  const newProps = propsMapper({data, loading});

  return (
    <WrappedComponent
      {...props}
      {...newProps}
    />
  );
};

So, now we can use it like this.

// create a mapper function
const propsMapper = propsMapper({data, loading} => ({
    postData: data,
    postLoading: loading
}));

const Post = ({postData, postLoading}) => {
    //.... render the post
}
// pass the mapper function as the second argument.
export default withPostData(Post, propsMapper);

You may also use renderProps, but I feel nesting in render props can be very confusing to understand. Here is an example.

return (
  <Title
    render={titleProps => (
      <Subtitle
        render={subtitleProps => (
          <Section
            render={sectionProps => (
              // ...
            )}
          />
        )}
      />
    )}
  />
);

Conclusion

Hoc is not an antipattern. This post only highlights how hoc can go wrong if you do not pay attention to naming the props. It is always better to have property names which are not generic or by implementing the propsMapper. By doing so, we reduce the chances of having conflicting properties. I hope this post will help you in building hoc's in a more effective way without the problem of property conflicts.

 

Author
Abhishek Saha
Abhishek Saha

Passionate about exploring the frontiers of technology. I am the creator and maintainer of Letterpad which is an open source project and also a platform.

Discussion (0)