Drawing a Refactor

The Context

On a React project, a dev came to me struggling to create a feature - we had a FollowButton used on multiple screens which a user could click to follow another user.
I want to share the process of visualising a refactor with a diagram and how it helped in conveying the magnitude of tech quality improvement. In the future the dev might be able to identify and carry out similar refactors to improve tech quality in the future (teach a dev to fish)!

The Problem

In our project, we separate business logic into hooks to separate rendering from logic (in this sense the functional components become humble and so the business logic is easier to test).
Hooks can extend each other and in this case we had a useFollow hook, which controlled some business logic to toggle the following status of a given user.
We had already implemented the follow button on a profile page (single follow button on the page - called <ScreenOne /> below). Here’s how the code looked initially:
notion image
useFollow returned a function toggleFollowing(userIdToFollow: string) => void. The screen-level hook useScreenOne has the information about the userId we want to toggle, so useScreenOne calls useFollow to get the toggle function, adds in the correct userId to get a callback, and passes that callback to an onPress in the FollowButton.
The new feature required adding a list of FollowButtons on a page (<ScreenTwo />) with multiple users being able to be toggled. The dev (understandably) followed the existing pattern and implemented the following:
notion image
We can see after adding a second screen, the pattern starts to become messy.
Additionally, what was creating a single toggle callback inside useScreenOne to toggle one user, becomes creating and passing down a list of callbacks in useScreenTwo, which increases complexity.

What does the diagram show us?

  • Arrows show code dependencies:
    • We see that high-level components (Screens) depend on low-level ones (Buttons);
    • The toggle function is passed through multiple hooks → high coupling;
    • The connected arrows know about the inner workings of other components (screen hooks know about some of the implementation details of the useFollow.
  • Coupling of screens - because both screens depend on the same useFollow, the two screens are coupled together. Trying to update functionality for <ScreenOne /> could cause a regression in <ScreenTwo />!
  • It was useful to note the Scope - how many users the functions relate to:
    • useFollow always only cares about dealing with a single user;
    • useScreenOne just used a single follow button, so still only a single user;
    • useScreenTwo however, cares about a list of users and needs to do some manipulation of the callback it gets from the useFollow (which only cares about a single user);
    • FollowButton component once again only cares about a single user (the one which it’s toggling, whether it’s just a single button, or in a list, it doesn’t care).

The Solution

To solve this, we co-located the useFollow and FollowButton and renamed useFollowuseFollowButton. Now FollowButton depends directly on useFollowButton in it’s own encapsulated component and we just pass down the userId to each instance of FollowButton.
notion image
  • The entire FollowButton component doesn’t care about where it’s being rendered at all!
  • The screens don’t know anything about the implementation details of what’s happening in the following functionality. If we want to remove/ change the follow functionality, we can do this without needing to change the useScreen hooks.
  • Follow functionality should always change at the same times for the same reasons, so there is little chance of introducing a regression between screens.
I have reduced the opacity of the things FollowButton no longer knows or cares about (no longer coupled to). Technically, I could reduce the opacity of everything outside the rightmost box, as FollowButton doesn’t care at all about any outside code, it just knows when someone passes it a userId it can toggle the follow status of that user.
I think this dependency graph shows clearly the value gained from the refactor, but also acted as a training interface to visualise and discuss the changes we were making and why this was an improvement to the system.