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.
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:
- ) 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.
- ) A
PATCHrequest should be made to the backend server to update said boolean to match.
- ) If the boolean on the backend is
true, the corresponding object’s data should be added to a
Watchlistcomponent 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
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
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.