Jesse Conner

Re-visiting CSV Magic

Posted: 2/4/2024

I still love CSV Magic; it was one of my first modern web projects. I use it often, but I always get the sneaking feeling that there is considerable room for improvement. Consequently, I have decided to dive back in and give it a bit of an internal face-lift (if that makes any sense). Refactoring the code for readability, maintainability, and performance. As I go through the process, I am going to journal my thoughts and feelings. My previous blog posts have been high level overviews, so this method should give more insight into my methodology and thought process.

My first order of business is updating dependencies. I had mostly stayed on top of it, but it looks like there are quite a few dependencies in the package file that are not (and possibly never were) being used. My goal is to get the project building and then branch off before I start doing anything intensive.

I have updated Webpack and now have an error indicating that I “Cannot assign an abstract constructor type to a non-abstract constructor type.” I had gone crazy with OOP principles when I first started working in JavaScript and React. It was likely because my schooling was more in Java and C#. However, that has led to an inheritance mess to clean-up. Alongside a dozen or so class components that should become functional components for conciseness.

With only one not-null assertion and some not-so-helpful-help from ChatGPT, I have gotten the app building again. Along with the over-use of inheritance, I have also noticed that I used TypeScript return type declarations throughout the app. That led to silly issues like the error above. If I had just let TypeScript do its thing and infer the return types, it would not have thought that I was going to try to display the abstract modal base class at some point.

My next order of business is to figure out why my Webpack configuration is so complicated. The app originally used create-react-app and moving it to vanilla Webpack brought along baggage, such as NodePolyfillPlugin. I have also discovered that I have multiple rules for CSS and SCSS extensions.

I have spent a long time questioning and playing with the configuration to learn that if I want to use CSS modules and certain Bootstrap styles, I do indeed need multiple rules. It gave me insight into how loaders and modules work, so I am thankful for that diversion.

With that out of the way, my next step is to look and see what ESLint says that past-me should not have done. There is a slew of not-null assertions to clean up, but the bigger mess that I have discovered is another product of my over-use of inheritance. All my modals were derived from an abstract modal. It has a function, onApply, that is the source of a substantial portion of the “unknown”s and “any”s throughout the codebase. When a modal had its confirmation triggered, a call was made to a function in the base class that accepted the arguments ...params: unknown[] and then, in turn, called an abstract property set on each concrete class named toCall. This would have been somewhat easy to fix with the Parameters utility type, except for the fact that all the sub-classes were coded to take a reference to the table as their first argument, whether they needed it or not. Consequently, I have decided that it is a better approach to replace that abstract class with a component that takes in the fields that pertained to how the modal functioned, and let the modals do whatever they wanted with it.

Now that I have gotten to the point where the app feels less fragile, I have decided that it is time to start looking into performance. I have decided to switch from using React useContext and an awful lot of prop drilling to zustand for my state/context management. The decision is primarily to simplify code, allowing me to track down an issue causing unnecessary re-renders. It also has the side benefit of giving me an excuse to learn something new. It feels nice and clean to centralize the state for open files and get it out of the display logic.

I am also taking the concepts introduced to me by the Next.JS app router with regards to component ownership. I am removing some unnecessary middlemen to avoid unnecessary re-renders. The sheer size of the DOM tree in larger files leads to a slow experience, so any re-render that can be avoided helps.

With the React side of things all cleaned up, it is time to investigate the inner logic and see what improvements I can make there.

The first thing that I have noticed is that I no-longer need to rely on lodash for deep cloning and that I can make use of window.structuredClone instead. In that same function I am taking an object and returning an object so I have switched that to a generic and consequently have the privilege of removing 11 different type assertions.

With that out of the way, I am at the point where I am only using lodash in one module. It is only beings used to generate a random number, so I have gone ahead and cut a dependency from the project.

Moving on to the column generation code, these are some of my first uses of design patterns. I am generally happy with my implementation aside from using some unnecessary enumeration and for directly defining some types that could have been inferred.

Now that the return types are being inferred, I sadly do not have the need for the fancy Factory that I got to make back when I was learning.

At this point I am much happier with the state of the application. There are still challenges to tackle. For example, now the add column dialog does not function correctly, and performance leaves something to be desired.

One of my takeaways from this refactor has been that I need to stay on top of testing throughout the development cycle. Many of the changes to structure that I made could have been more smoothly and confidently introduced if there was not the need for rigorous manual testing. I want to write a whole blog post on testing later. I will save my other observations for that.

Please feel free to look at the source code and explore, experiment, and contribute if you so desire. CSV Magic will always hold a special place in my heart, and I am certain that I will come back to it again in the future.