Customer Portal v2.0 - Context vs HOC

When I started at Weevio back in 2016, I was in my senior year of college at Indiana University. Initially, I was assigned relatively easy single page applications using just HTML, CSS and JavaScript. After I completed the full stack developer onboarding process, I was tasked with my most complex project yet: adding a new feature to the Customer Portal.

The Customer Portal was built using server generated pages powered by handlebars.js. Essentially, each route retrieved the required data from the database, rendered the HTML using handlebars and then it was served to the client. While such a solution was common at the time, as a Junior Developer, I realized it was not very scalable. The lead developer and I wanted to use React to rebuild and modernize the portal but unfortunately, we had a huge backlog of projects and issues.

Fast forward to 2022, we had completed several projects and even developed new applications in React. We had a good grasp on React and there was finally enough time to start working on Customer Portal v2.0. I was tasked to lead the design and development of the project (Weevio has only two engineers, myself and my friend and mentor, Andrew).

Alright let's get started..

For this new version, I wanted to make sure the UI and data-fetching logic were decoupled. This would also allow us to have a more agile development process. Any changes to the data-fetching logic could be made with minimal impact on the UI, as long as the API contracts are maintained.

The customer portal's primary API requests involve fetching orders, quotes, and service repair orders. I wanted to make the data-fetching logic reusable for all order types. I wanted to avoid redundancy, increase reusability and improve readability (DRY). I did some research on React patterns to find a solution. After some research it came down to Context and Higher Order Components. Let's compare the two and see if you can guess what I chose.

Let's start with Context. Typically, variables and functions are passed down from a parent component to a child component via props. However, there could be many child components between the parent and the target child (prop drilling) or there could be many children that want the same data. Context is designed to provide a way to share values (like user data, themes, preferences) across the entire component tree without having to explicitly pass props down through every level. It allows a parent component to easily provide data to close and distant children.

Higher Order Components (HOCs) are functions that take a component as an argument and return a new component with added properties and functionality. They are great for reusing logic across components. HOCs tend to help keep components clean and focused on their responsibilities.

Ok after looking into both options, it seems like either could work. To help make our decision easier, let's see what an implementation could look like.

We will start with Context.

Let's discuss. What I did was create a Context.js file this is where I will define the functions for fetching different order types. To avoid unnecessary re-renders, the functions have been wrapped useCallback. For demonstration purposes, I defined just two functions getOrders and getQuotes. Both functions are faking API requests with the help of setTimeout. The functions are then put into an object and supplied to the OrderManagementContext.Provider value prop.

Let's go over the consumer component Orders. On first glance, I think you may be a little surprised at the amount of boilerplate code required to make use of getOrders function. I was. First, we define some local state to hold the orders data after the request. Then we call useContext(OrderManagementContext) to gain access to getOrders. But there is one more step. We need to use useEffect() to perform the request once after the initial rendering.

Ok so this could work. Let's take a look at the HOC implementation.

We have a few more files to look at for this implementation. Let's start with Service.js. This file defines the Service class which has one method, getOrders. It's faking an API request to fetch all the orders. To keep things simple the class has only one method. This is where I would define the other methods for fetching quotes, service repair orders or any other related API requests.

Next let's take a look at withData.js. The first thing we do after the imports is instantiate the Service class. We'll need its methods for retrieving our data. The HOC is withData. It accepts two parameters, a component and a function to fetch data.

The useEffect in the HOC contains the logic that calls the provided function to fetch the data. We then update the state and pass data, isLoading and error to WrappedComponent so that the component only has to worry about displaying the appropriate UI to the user.

One of the drawbacks of this pattern is React Developer Tools no longer renders the original component's name in the component tree. To fix this I defined the function getDisplayName . It takes a component and returns its name. Then we redefine the display name of ComponentWithData by including the original component's name wrapped in parenthesis. Now searching the component tree to view the props of your original component won't have you pulling out your hair.

Finally let's see how this all comes together, open App.js. First, I create a new instance of the service class. Next, we call withData (HOC) and provide two arguments, the Orders component and service.getOrders method respectively. If we open up Orders.js we see that

What I love about the HOC solution is that is abstracts the API related logic away from the components. This keeps the Orders component clean and focused on its responsibilities. Additionally, when it comes time to develop the Quotes component, we simply add a new method to the Service class and we're off.

Ok that was a lot I know. Let's close this out.

...and the winner is... Higher Order Components!

So why not Context? While on the surface it appears to be the simpler solution. It's fairly easy to setup and understand. However, I really don't like that it tightly couples the components that consume the context. This reduces the reusability of our components. The consumers also become harder to test in isolation. Additionally, if we need to do any refactoring down the road changes to the context's structure or logic may require updates across the consuming components. Applications tend to get more and more complex so this could potentially slow down development in the future.

On the other hand, HOCs are decoupled from the components that they enhance. As you can see the Orders components only has to worry about rendering based on its props. This also makes the components significantly easier to test in isolation. HOCs encourage code reusability and abstraction, this keeps components clean and focused on their responsibilities.

In conclusion, after comparing HOCs and Context, it becomes clear that HOCs for this case offers a more versatile and reusable solution for enhancing components. While both approaches have their place in React, the decision to choose HOCs underscores a commitment to modular, maintainable, and scalable code.

Thank you for reading!