It’s not every day that you get to work on changes that affect the entire codebase. Today, we’ll be going over a large scale optimization that we have performed on the WebPro Panel.
The WebPro Panel is ResellerClub’s portal to help web professionals manage orders they have purchased on our platform. It is built in React and allows users to manage their hosting and domain orders.
The panel itself is made up of multiple pages, each housing a particular set of features. As it happens, building a management panel over time incurs tech debt that adds maintenance overhead. Additionally, it contributes to negative user experiences with slow, laggy or intensive interfaces.
Once we decided to clear our tech debt, we noticed multiple pages and user flows that could be refactored to reduce both maintenance overhead and also make the user experience reliable and fast. In this article, we will be covering the Order Management page that is used by the web professional to perform actions on their order.
The Order Management page on the WebPro Panel is the busiest part of the panel. It deals with a mountain of actions that are directly proportional to the number of orders on the page.
The above image shows how an order is represented in the WebPro Panel. Every action that the user can take is separated into individual units that are named ‘Widgets’. Depending on the order, the panel displays a combination of widgets and lays them out in a grid-like fashion.
Each widget is isolated from the other. It doesn’t know which other widgets exist alongside it and this is intentional – for a single order, depending on the state it is in, it is necessary to show X set of widgets and that set can keep changing throughout the lifecycle of the order.
How does a widget work?
There are a lot of different pieces that go into making a widget work. For this optimization though, we focus on one piece – data. Each widget is tasked with rendering some part of the information that comes from the server. For this to work, each widget lists down the data dependencies it has, where each dependency is a pair of (endpoint, responseTransformer). The average size of this list across all the products would be one. So for the above layout, the average number of API calls that are needed to render all the widgets is 5.
How do we update a widget’s state?
Whenever the user takes an action, we need to update the widgets to reflect the current state on the server. In our initial implementation, we went ahead with the simplest solution to update state – reload everything. Whenever an action is dispatched that would change the state on the server, we remount the widgets which results in them refetching their data dependencies.
The simplest solution isn’t always the best one. When we decided to optimize Widgetry, we looked at how the data dependencies were handled and asked the following questions:
Is it necessary to reload every widget whenever an action is taken?
At first, it seems like no would be the correct answer, but the right answer would be it depends. While it is intentional that each widget is isolated from one another, the data they consume from the server isn’t isolated.
Almost all widgets fetch their information from a single endpoint and hence whenever an update is done, it becomes necessary to reload all the widgets since we don’t know which widgets need to be updated which brings us to the next question,
Is it possible to know which widgets need to be updated on a particular action?
The answer to this is yes!
We finally had an avenue to optimize on and we set out to look for a better way to declare a widget’s dependencies – one that can accommodate information about when a widget needed to update. On the redux front (which we use for some parts of state management), we were able to track which actions would cause a change at the server and decided to list these actions along with the widgets which would be affected by them.
The earlier pair of (endpoint, responseTransformer) now looks something like (endpoint, responseTransformer, reloadableActions). With this change, we can now determine which actions cause which widgets to update.
After applying this change, we noticed that not all widgets need to be reloaded whenever an action takes place. While this is good, this optimization alone is not good enough. Keeping the above widget layout as our reference, let’s say our optimization reduced the number of widgets that would be reloaded from 5 to 3. This is good but there is one little problem – All 3 widgets request the same endpoint. So we’re making the same API call 3 times, which is clearly not the most optimal scenario.
So how do we deduplicate the calls? Can we make sure that we end up making only the optimal number of calls we need to make for any given scenario? This will be covered in the next post and we’ll also be going over the results of the optimization.
Check out more articles that can help you develop, secure, host and offer incredible tech tips for your website!