Hey there!

If you missed my first post on advanced react patterns, I strongly suggest you check it out. A quick skim through the article will give you just enough context

It was a sunny beautiful day. Summer was here and I got to work excited, ready to crush the day. My goal for the day was seemingly simple: push the new product we’d developed over the finish line, and have it on production before the end of day.

There was just one problem.

Most companies work with some sort of shared frontend component library, and mine wasn’t different. One of the tasks left was to make sure users could see our new offering in their basket breakdown while checking out.

The Breakdown component in the company-wide shared library looked like this:

user purchase breakdown
user purchase breakdown

If you went ahead to fill the mockup with example purchase items, it would look like this:

Purchase breakdown with items and prices
Purchase breakdown with items and prices

Before you write this off as a basic component, each basket “breakdown” could have even more details about the user purchase:

Purchase breakdown with icons and discounts applied
Purchase breakdown with icons and discounts applied

As with real world components, there were many considerations handled within this Breakdown component. Internalisation had to be provided for users with varied languages, and a lot of business logic sat within this component.

For now, I’ll keep this simple so we can study what the problem was and reflect on a possible solution.

When the Breakdown component was built, it was implemented to rely heavily on passing “more props”. By this I mean no disrespect to the engineers who implemented this, I have a lot of respect for present and past colleagues, as I could have done the same.

Anyway, the Breakdown component ended up with an exposed API like this:

<Breakdown
			travellers={{
				adults: 2,
				children: 1,
				infants: 1,
			}}
			discountCards={['discount #1', 'discount #2']}
			price={{
				currencyCode: 'USD',
				total: 12000,
				breakdown: {
					fare: 600,
					baggageInsurance: 3600,
					seatUpgrade: 450,
					discount: 250
					// ... more prices go here
				},
			}}
			isRoundtrip
         // ... more props go here
/>

So harmless, right?

Unnecessary Modifications

I do have a lot of problems with relying heavily on just “more props”, but the most annoying while working on the Breakdown component the other day, was the unnecessary modifications that had to be done anytime a new item had to be displayed within Breakdown.

So, what was the exact problem?

Within the Breakdown component, each item and price list was a sub-component called BreakdownItem. This BreakdownItem was rendered internally within the exposed “Breakdown” component.

As a user of the Breakdown component, you had no access to BreakdownItem as it wasn’t part of the exposed API.

Furthermore, each BreakdownItem was rendered internally based on some business logic such as:

// simplified 👇

{hasBaggageInsurance && 
    <BreakdownItem label="Baggage Insurance" 
 					{...morePropsGoInHere}/>}

To add a new item to Breakdown, you’d have to add a new prop:

<Breakdown
			travellers={{
				adults: 2,
				children: 1,
				infants: 1,
			}}
			discountCards={['discount #1', 'discount #2']}
			price={{
				currencyCode: 'USD',
				total: 12000,
				breakdown: {
					fare: 600,
					baggageInsurance: 3600,
					seatUpgrade: 450,
					discount: 250
					// price for new item 👇
 					newItem: 31
				},
			}}
			isRoundtrip
         //more props here 👇
         newItemLabel={label}
/>

Then you go ahead and render a new Item internally within the shared component library.

// in Breakdown component 👇

{someUILogic && <BreakdownItem label={newItemLabel} {..otherPropsGohere}/>}

This is crazy. You’d have to wait for the library version to be updated after issuing a new PR, passing all CI checks, and ultimately getting a new version of the shared component library released before you can use it.

long process owing to modifying the component library

This is suboptimal and kills productivity. A shared component library is supposed to save you time. This particular component wasn’t.

A Solution

A better API for this breakdown component could something along the lines of this:

// expose both Breakdown & BreakdownItem to end users

import Breakdown, {BreakdownItem} from 'component-lib'

// let them compose these themselves
<Breakdown>
   <BreakdownItem 
		label="Trip Fare" 
		price={600}
		{...otherSpecificPropsForItems} />
   <BreakdownItem 
		label="Seat Upgrade" 
		price={450} />
</Breakdown>

With this implementation, unnecessary modifications don’t have to be made each time a new item has to be added to the Breakdown component. You just go ahead and render a new BreakdownItem and passing it specific props related to the item.

import Breakdown, {BreakdownItem} from 'component-lib'

<Breakdown>
   <BreakdownItem 
		label="Trip Fare" 
		price={600}
		{...otherSpecificPropsForItems} />

   <BreakdownItem label="Seat Upgrade" price={450} />
   <BreakdownItem label="New Item" price={330} />
</Breakdown>

Conceptually, this is simple. However, implementing this the right way would be achieved by leveraging the compound components pattern which advocates exporting parent and child UI components, and the relationship between these reflected by managing UI state in a central place, the parent component.

The UI state is then communicated to every child components without having to rely on props. This allows more flexibility.

Conclusion

In my Udemy course, the complete guide to advanced react patterns, I dedicate an entire section to what compound components are - in plain, approachable language. I discuss why they matter, and how to implement them with real-world examples.

In the next lesson, I’ll address another real-world example I hinted at in the first lesson.

I’ll see you then!