Decluttering a React Application

We started working on our React application a few years ago. We started with Flux, but shifted to Redux when it became available. Over time, we moved from Redux-Form to Formik, from Moment to date-fns, from Victory to Nivo to name just a few. In fact, a significant part of our code now uses GraphQL so state management with Redux is reduced. We were learning as we went along and the need for new features often precluded the opportunity to clean up working code.

As a result,  the time was ripe for an exercise in decluttering the application. At the start of the exercise, the application had more than 500 react components and hundreds of reducers and actions. So, this wasn’t a small application.

The challenge was to remove unused code and to reorganize existing code so that it was smaller and easier to understand and maintain.

To get started, we used the utility lxjs and a commercial tool Lattix Architect to create a map of the dependencies of our application. For any file, it allowed us to see what it imports and who imports it. It was easy to follow dependency chains. The underlying data comes from madge that can be freely downloaded.

Remove Unused Files

We quickly identified files that were no longer in use. Of course, it wasn’t enough to remove just the unused files but we also had to follow it up for code that was used only by these files.

Organize components

Components were the largest part of our client code. Roughly 75% of our client code was components. This is where the most work went into. We ended up dividing the components into two parts: view and components. Views were the top level components that often corresponded to pages while the lower level components were the shared components used by views. Components that were specific to a view were normally kept within that view, but we weren’t rigid about it. When we were done, we ensured that there were backward dependencies from components to views.

Clean up actions and reducers

As our application evolved, many actions were no longer in use. Since actions are used by components and reducers, looking for unused actions is not straightforward. We had to identify all actions that weren’t used by any component. It helped that we had strong naming conventions for our actions and reducers. Once unused actions were removed it was easy to identify and remove all reducers that referenced those unused actions.

Remove unnecessary couplings

Selectors were a source of unwanted couplings. While most of our selectors were in reducers, there were a few that were in components. We simply co-located the selectors with the reducers. This helps encapsulate state from the component. Sometimes selectors use other selectors. This creates a coupling between reducers. We co-located selectors with reducers to avoid creating a cyclic coupling between reducers.

Ad-hoc helpers were another source of unwanted coupling. There were helpers that were created to support one component initially, which were then used by other components. For instance, any component that dealt with date or time selection, had to deal with time zone adjustment. Initially, many of these helper functions were part of the time zone selection component. These functions were moved to a separate helpers directory for use by all components.

Cleanup Styles

We had more than 50 css files. Most were located in a separate styles directory but some were colocated with components. We got rid of unused css files easily. However, it would have been a bigger effort to rationalize the multiplicity of styles used by different components. We left that for another day.

Lessons Learned

It’s easier than it looks. This was a surprise. Cleanup is perceived as hard and thankless that doesn’t add to the bottom line. But it is technical debt which takes an incremental bite everytime you go to make a change. Initial estimates were that this would take many months. In reality, the bulk of this exercise was done in two weeks.

Know your code organization. One of the valuable by-products of this exercise is that it aligns the current team. You can often organize the code by feature or by type. We used a combination of these two approaches at different levels. However, we always mediated both approaches by dependencies to ensure that we weren’t introducing cyclic dependencies.

Reducing the bundle size isn’t the goal. This is not an exercise to reduce the bundle size, even though it did help us get rid of a few libraries. However, that was a by-product. Multiplicity of libraries produces its own form of clutter. For instance, our application uses both Redux-form and Formik. To get rid of one or the other would help reduce the bundle size and reduce the clutter but we kept it outside the scope of this effort.

Why do we accumulate clutter

Often code starts out as well organized but erodes over time. It is easy to blame deadlines and business needs but they do not fully explain this common phenomenon. Other reasons include:

Programming culture doesn’t emphasize cleaning up. We solve business problems by using the right combination of technologies. We write tests. We then move to the next issue on our sprint. Cleaning up isn’t measured or valued in our development processes.

For current programmers, it is not a priority. The problems arise as newer people come in or as new functionality is added that others aren’t familiar with. Without an organizing principle for the code and without a culture of continuous cleanup, code rot becomes inevitable.

Programmers minimize changes to code that they didn’t write. This is true even for expert programmers. It is also easy to understand from a practical purpose. Moving a helper function from one file to another requires changing unfamiliar files. Removing a piece of code requires understanding why it was created in the first case and ensuring that it is unused. All this distracts from the task at hand and finishing up our issues for the sprint.

What we achieved

At the end of this exercise, we ended up reducing the size of our code by about 30%. There were half as many actions and 40% fewer reducers. The number of components also came down by about 10%.

Years of accumulated clutter had been dramatically reduced.

Leave a Comment