Keep react component complexity in check with inversion of control

francodalmau

Franco Dalmau

Posted on March 13, 2024

Keep react component complexity in check with inversion of control

I'm sure at one point in time you've found yourself sitting in front of a desk cluttered with random things! how did it get to that point? Slowly but surely, you've been placing more and more stuff on a perfectly tidy desk until it turned into a pain to fix.

This is similar to what happens in React components. We start simple and clean, but as we add more features the code becomes messy and hard to maintain.

Let's see one way we can prevent that.

Starting with a (simplified) confirmation prompt like this one:

const ConfirmationPrompt = () => (
  <div>
     <p>Are you sure?</p>
     <div>
       <button>Cancel</button>
       <button>Confirm</button>
     </div>
  </div>
)

// Used as <ConfirmationPrompt />
Enter fullscreen mode Exit fullscreen mode

We later find ourselves asking confirmation for an action that is critical to our system. We can add a warning message to emphasize the importance

const ConfirmationPrompt = ({destructive}) => (
  <div>
     {destructive && (<p>☠️ DANGER ☠️</p>)}
     <p>Are you sure?</p>
     <div>
       <button>Cancel</button>
       <button>Confirm</button>
     </div>
  </div>
)

// Used as
<ConfirmationPrompt destructive />
Enter fullscreen mode Exit fullscreen mode

Things seem good, but what if some actions are dangerous and need to trigger an email notification? We could just copy-paste the same logic, but notice that we quickly run into one BIG problem:
The possible variations of our component are growing exponentially

const ConfirmationPrompt = ({destructive, triggersNotifications}) => (
  <div>
     {triggersNotifications && (<p>ℹ️ Email will be sent</p>)}
     {destructive && (<p>☠️ DANGER ☠️</p>)}
     <p>Are you sure?</p>
     <div>
       <button>Cancel</button>
       <button>Confirm</button>
     </div>
  </div>
)

// Used as 
<ConfirmationPrompt
 destructive
 triggersNotifications
/>
Enter fullscreen mode Exit fullscreen mode

Our first example had 1 possible state, the second had 2 (destructive and non-destructive) but the third now has 4!

Remember: patterns stick and code that has changed is prone to keep changing, so it's best to address this early.

Here's where things get interesting. Instead of the component acting like a mind reader, guessing what other parts of the code will need out of the ConfirmationPrompt, we can flip the script and make them decide!
This simple and powerful tool is called inversion of control.
Essentially, we're giving back control to the user of our component, making it more flexible and adaptable.

It looks like this:

const ConfirmationPrompt = ({header}) => (
  <div>
     {header} //Our caller decides what to put here
     <p>Are you sure?</p>
     <div>
       <button>Cancel</button>
       <button>Confirm</button>
     </div>
  </div>
)

// Used as 
<ConfirmationPrompt
 header={
    <>
       <WillNotifyMessage/>
       <DangerZoneMessage/>
    </>
 }
/>

// or

<ConfirmationPrompt
 header={<WillNotifyMessage/>}
/>

// and any other use case that comes up!
Enter fullscreen mode Exit fullscreen mode

Alright! Our original component is kept short and concise while the rest of the codebase is spared the burden of knowing how ConfirmationPrompt should be configured.

Sadly, it won't help us in keeping a clean desk but inversion of control has many applications in React and you'll find examples with different variations. The idea behind it is always the same:

To give control back to the caller of the function

💖 💪 🙅 🚩
francodalmau
Franco Dalmau

Posted on March 13, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related