Clean Code UI Refactoring at Axon 🎉

In this post, I would like to share my experience on how I did refactor fairly messy React's context based codebase into clean, nicely encapsulated abstractions backed by SOLID principles and application of several well-known design patterns. Stay tuned and read on guys 😎

At Axon, we provide a solution for defense departments around the world to view different kinds of evidence in the web namely audio, video, image, document, log. The latest exciting of supported file format is zip (compressed evidence). I have been part of UI code maintainer group at Axon's DEMS (Digital Evidence Management System), we are responsible for assuring high code quality review before shipping our products to production. I got a chance to review with the team that was developing ZIp viewer for the last several months. The bellow is their stack choices of technology:

  • React's Context
  • React's Hook
  • Redux (it's actually just a fancy React's Context underneath the hood 😆)

First of all, I have never been a fan of Flux / Redux architecture given its terrible verboseness of boilerplate we have to write. Redux toolkit came into the picture lately to heal things up, but it's still Redux at very core in which suffer from awkward choice of merging local state and server state into one. Things will get even worse when you want to cache something or integrate with WebSocket... That's where React Query really excels at. It's my go-to recommendation for any application that deals with data fetching Restful protocol. But, you are most likely have maintained at least one application that have used Redux in recent years since it's been blooming back in 2015 via this talk. I'm right with you, many projects at Axon are also suffering from the same issues with Redux's drawbacks and lack of principles from the very beginning until style guide appeared apparently. Another issue with Redux lays in its implementation of O(n) time complexity in every run by default. It's not scoped by nature and high performance in comparison to Vuex. Solutions like Redux-ignore does help a bit but still... I think performance should be built-in instead of letting community brainstorming which leads to exploding amount of solutions.

Secondly, the team chose to use React's context for everything at the top level which they thought it could help them avoid prop-dripping. This comes at the const of breaking encapsulation and potential for code reuse in other places. It also leads to many problems specifically:

  • Context Provider easily became a god object which knows every little detail of child components
  • Simple state such as toggle show / hide modal is also included in the context which has nothing to do with other logic
  • There is zero chance of code-reuse and composition since every child components stuck with consuming specific context

I started it by convincing the team that React's context and Redux are not the right tools for the job with the reference to React's official guide. After that, I kicked things off by moving all pieces to where it belongs by purely using local state. It's actually very similar to actor model for UI development 😄

Redux Versus Actor

You also might want to spend sometime to enjoy the brilliant content of this video 😎

I also explained several principles applied along the ways to the team such as SOLID in component architecture, Lifting State Up actually is Mediator pattern in disguise and should be used more often, Factory Method pattern is also very useful for grouping a set of similar components at the point of creation (e.g. Icon set), Strategy pattern plays a key role in selecting the right evidence viewer for us (there can be any video, image, document, etc inside a zip evidence), backtracking algorithm is a very effective way for us to collect all the paths that contain items matched arbitrary predicate in a tree data structure (i.e zip file). The demo below shows how codebase looks before and after my big shot of refactoring:

As a result, we now have variously dedicated components (actors) which are in charge of specific tasks in our component architecture. We can easily let them communicate by talking to each other (trust me, prop-drilling is no biggie at all), reuse else where and tested in isolation.

There you have it guys, my story on how I untangled messy codebase into clean, encapsulated, nicely formed abstractions for maximizing code reuse and maintainability . With that, I hope you guys learn something useful, avoid the same mistakes we made and enjoy problems solving in this world 😇

❤️❤️❤️ Be well, Be happy ❤️❤️❤️