Keep react component complexity in check with inversion of control
Franco Dalmau
Posted on March 13, 2024
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 />
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 />
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
/>
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!
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
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
October 2, 2023