A Thousand Ways to Skin a Cat in Javascript: The Value of Refactoring

If there’s one thing I can say for certain that I’ve learned in my first month studying Javascript, it’s this —

There’s rarely just one way to solve any given problem.

In the earliest days of my bootcamp experience, I’d often find myself leaning on tools that I already felt comfortable with in lieu of implementing new ones that might be better suited for a specific problem. Whether it was falling back on my old reliable forEach loop instead of utilizing a more specialized array mapping method, or failing to take advantage of the implicit return offered by an arrow function just because I was so used to coding my callbacks the long way, I’d assure myself that as long as the end functionality worked - the path I took to get there was irrelevant.

This method was no better illustrated than in my first pass at a new React project, where when mapping out the data flow between components, I decided to stick to an entirely top down style that I’d had success with on smaller applications — irregardless of knowing that this new one would be much more complex. One big bowl of spaghetti code al dente and a couple of easily avoidable bugs later, I came to understand that just because you can skin a cat a thousand ways in Javascript definitely does not mean that some of those methods aren’t better than others.

The Cat

The functionality I needed to implement was seemingly simple. A user should be able to click a button on the webpage and cause a few things to happen:

  1. ) The text on the button should read either “Watch” or “Unwatch” depending on the value of a boolean on the backend, and toggle to the opposite value when clicked.
  2. ) A PATCH request should be made to the backend server to update said boolean to match.
  3. ) If the boolean on the backend is true, the corresponding object’s data should be added to a Watchlist component on our site. If the boolean is toggled back to false, it should likewise be removed.

If the approach you imagined in your head seems pretty straightforward, that’s because it probably should be! But I have a knack for making simple solutions far more complicated than they need to be. Let’s take a look at my initial approach.

How Not to Skin Said Cat

Because of my aforementioned propensity for staying in my comfort zone, I decided that I’d be just fine repurposing the “do all my work at the highest level and trickle the results down to the children that need them” approach that I’d used on projects with just one or two components. That meant that at my top level App component, I was making my initial GET request to populate the array value of a state variable;

Mapping over that array to create a PoliticianCard component for each object within;

Filtering over that array to find the objects that should be included on the Watchlist, and then mapping over that new array to create the PoliticianCard components for each;

Before finally passing the resulting arrays down to the corresponding child components that needed them.

Issues with my approach became clear shortly after I began tackling the functionality of the Watch button. In the spirit of overthinking the problem, I decided to create a state variable assigned to the value of the isWatched boolean on the respective server object that I would use for the ternary logic in rendering the button, as well as a separate one to house the server object itself.

All of this mess combined with the data flow structure I had decided to doggedly stick with meant that, within one single button click, I had to update the state of my isWatched variable to the proper value, PATCH to my backend to toggle the value of isWatched there, and then inversely set the state variable in the parent component to match the newly updated server array and re-render the various arrays it was passing down to various children in turn. Yikes.

If you’ve done much work with state in React, you’ll know that if you try to update a state variable and another value that relies on that state variable within the same function, you’re going to have a bad time. And because the function I was calling on click was ostensibly updating the state of isWatched , politicianData , and politiciansArray all at once, bad times were certainly had. To save you the headache I went through in cobbling together a working solution built on this duct tape and cardboard foundation, I’ll just show you where I landed. And while it may have “worked” from a functionality standpoint, it’s painfully inefficient.

If somehow you got through all that, God bless you.

It’s not pretty. Calling useEffect multiple times because my dependency wires are all crossed, defining several unneeded state variables that just muddy up the code’s functionality, super messy data flow structure — it just doesn’t read or operate nicely at all. On top of that, because I was doing all of my array mapping work at the top and passing the results down to several individual children, I was actually creating unintended bugs in other components that I hadn’t even found yet!

At this point, I was forced to take a step back and reconsider my process. I knew that even though the feature *technically* worked as intended from a user perspective, there must be a better and more efficient answer. I knew what had to be done.

It was time to refactor.

Cat Skinning 2: Electric Boogaloo

I realized that much of the confusion in my code stemmed from the fact that I was trying to spread one source of data too thin. By making a fetch to my full server array only once at my top level and then mapping various subarrays from that single source to be passed to multiple different children and interacted with in different ways, it was no wonder I was running into problems at every turn. I even realized later on that this data flow structure was causing a problem completely unrelated to my Watch button woes in a completely different component.

With all that in mind, my first step was to break off individual calls of useEffect and an initial GET request to each component that needed access to the info. This way those components could always get their hands on the latest and greatest in terms of server data each time they’re rendered, and are able to individually manipulate that data on their own as needed. That means Watchlist gets its own politiciansArray variable that it can map and filter against to render only server objects where isWatched is true , and Politicians gets its own politiciansArray that it can check against the values of two drop downs to filter accordingly (one of the hidden bugs I alluded to before — since the filter state variables originally had to live up above in App in order to be able to check against that single top level politiciansArray , the filtered items being displayed got wonky when switching between pages. Moving the state variables and GET request down into Politicians allowed me to fix the bug).

The button functionality in PoliticianInfo received a nice facelift too. Rather than basing the ternary logic on a second state variable, I realized that the component already has access to that state through the full politicianData object that’s being populated from the server on the initial page load. That meant I was able to remove the need to update that additional state when the button was clicked, allowing that click event to instead just make the PATCH request directly without the need for a second side effect.

Because by this point I had already relegated the duty of making the necessary server request to each individual component, there was no need to pass down a superfluous callback function from the parent that existed only to trigger a second side effect and update the politiciansArray variable there. The necessary server change would be made on the Watch button click, and then each time a user opened a new page, that page’s component would grab the data it needed.

The Refactor Factor

Before committing to the idea of cleaning up my codebase for this project, “refactor” had always felt like a bit of a scary word to me. After all, if it ain’t broke — don’t fix it, right? Why risk damaging a functional feature just to make the wires under the hood a bit less tangled? But the value in the process that I hadn’t seen before was that, in cleaning up code just for the sake of our own sanity (or the sanity of anyone else who might find themselves working on it), we also force ourselves to reassess our own process and consider if there might be another path that not only looks better, but works better too.

And that was the beauty of the refactor — Because I decided to approach the problem from a different scope than what I normally felt confident in, I was able to both better understand the value of that scope and become more confident in it. Sometimes forcing yourself out of your comfort zone is actually the best way to feel more comfortable.

Author’s note: No cats were harmed in the writing of this blog. I love cats.

Full Stack Software Engineer