Component Composition - How to Keep Things Maintainable
How you can efficiently create composite components that are easily maintainable
July 24, 2021
Coding Practices
Components
When used right - components can be really useful. However - sometimes you can fall into a trap with some components getting too customisable.
I think that the biggest example of this would be the card component. As lots of websites have lots of varying different cards on the site - they usually don’t just stick to one.
For example, take a look at these two components - they are different, but they both contain similar features.
These components are based of the financial savings app Moneybox. The only affiliation with them I have is that I have some accounts with them. They didn’t pay me anything to use them, I just thought that their product and design is cool.
But take a moment to think, how would you create them? Would you create a single component that supports both of them? How would you differ between the two? How big would the component be in terms of size and logic? Would there be any?
There are multiple ways you can approach this - and they would all get the same result.
The first is creating two separate components - with no child components. This I would say is probably the least best (a politer way of saying “worst”) way of doing it. Issue with this being - is that you would efficiently have duplicate code. And this is a problem if you need to make a change that affects all of your cards. Say the font-size
was to change for example - you’d need to change it in two different places.
The second - better, but somewhat more complex is to create a single component, and the pass through what you want to display as props. And that’s fine for some components - atomic components like buttons, and input fields work great for this, but at the same time - you would need to define and manage a lot of props logically.
For example - how you reference the component may look like this:
<Card
balance={'£3,127.88'}
currentEarnings={'£625.57'}
interest={'£0.4468'}
interestRate={'0.4%'}
poweredBy={'OakNorth'}
/>
<Card
balance={'£3,127.88'}
hideBalanceLabel={true}
currentEarnings={'+£331.27'}
currentEarningsPercent={'+14.18%'}
/>
Counting individual/unique props - there are seven different props. Your card component would need to have logic to defer between what to display, and what not to display. Even if you were to reuse components, you would run into issues based on hiding or showing specific components.
The third way you could do it - which I personally think is the best way, is to re-use components which allow you to add more components as children. Here’s an example of the second card - the Card
component is simply a wrapper which applies styles (In this case padding and a drop shadow).
<Card>
<CardRow>
<Balance value={'2,231.27'} noLabel />
<Button text="Breakdown" />
</CardRow>
<CardRule />
<CardRow>
<span>Current earnings</span>
<span>
<strong>+£331.27</strong> | <strong>+14.18%</strong>
</span>
</CardRow>
</Card>
The result of this - means that the logic of what to add is taken directly out of the Card
component, and in this case put at the top level.
Something which I want to point out here is that I’m using styled components for these components, but just to avoid any confusion - I’m only going to include actual components. As well as that - I’ve also hardcoded quite a few values here and there, just to speed things up.
So - Why Should I do it This Way?
Simply put - it’s easier. Well… I think so anyway.
Anyone who has worked on a project which has frequent updates will commonly come across an issue where a component can be modified to meet the needs of the client or design.
But sometimes it can be somewhat of a complicated request… For example, the client would like you to change the appearance of a button, but only for users in a specific country. Or to hide something entirely if there are certain factors.
If your component is more “closed” and has logic inside it - that’s where things can get interesting. Card components are a really common use case for this I’ve found - not all cards are exactly the same as each other - and it’s not even down to content. Some cards may display other sub or child components, and it’s that where it can make components more difficult to maintain.
Now, having more closed ended components does have benefits, for example, it’s a really good way to prevent things which shouldn’t be in the component, but at the same time - some components can benefit from having the option of anything.
For example, a breakdown of the code is that there is a card component - which literally contains nothing.
Not on the component:
Not even in the code:
// Card.jsx
const Card = ({ children }) => <div>
{children}
</div>
By doing it this way - it means we’re not filling up our actual Card
component with logic now - and even better, we won’t need to in the future.
For example - here’s another card, one that is totally different to our existing cards. Sub-components have been added, and removed.
This component is remarkably different - and if we were to add onto our existing seven components, it could potentially bring up our total to 11 (fundIcon
, fundName
, fundPrice
, fundPriceDate
- although admittedly you could just put these in an object 😁)
The thing with this approach, is that once the new components are created, I didn’t need to modify any of the others. I created a Fund
and Chart
component, and I didn’t need to go back and modify any of the other components we already have!
The code for this component is:
<Card>
<Fund />
<CardRule />
<CardRow>
<span>Fund price as of 25/07/21</span>
<strong>£2.61</strong>
</CardRow>
<Chart />
</Card>
So - it speeds up time, and it also makes things less complex? What’s not to like! 😀
So - How do You do it?
My advice would be to keep things as open as you can. Allow other components to be used within other components - dynamically. Instead of passing through and managing a load of props - you’re passing through a load of components, with those props.
For me - the way which I’ve been coming to a conclusion of using the children
props is if I answer “Yes” to any one of these questions:
- Taking the design into account - are there lots of similarly designed components with children that I can create as one?
- Is there going to be a large amount of variations for this component?
- In the future - would there be any requests for changes to any of the different states or variations that would cause the DOM to be restructured?
- Does the client or content manager have control over the component? Would they potentially need content amends involving components resolved quickly?
If the answer to any of these is “Yes” - then it would probably be beneficial to create a component which would allow you to add anything as children.
The actual components are very straightforward - create a component, that accepts a prop of children
- and then reuse that component with your child content.
const Component = ({ children }) => <div>
{ children }
</div>
const App = () => <div>
<Component>
<h1>You can put anything in here!</h1>
<p>Images too!</p>
<img src="https://placehold.co/248x248" />
</Component>
</div>
If you need to reuse the contents of the App
component more than once - it would be beneficial to put that into a component. But - only if there is logic. If there isn’t, you could easily modify the components you have reused already to make a change everywhere.
Wrapping Up
Hopefully you’ve fond a new way of creating re-usable components for the better! I wrote another blog post on code-complexity and this is more for the frontend UI side of it! By making sure your components are re-usable now, as well as in the future is a massive help. Not just for you - but for other developers too!
This post has been on the back of my mind for a loooooooong time. To long maybe. In the future I might just focus on smaller things just so I can write things out - but if just like in real life, if I find something I like, I can’t stop talking about it.
This is also my first blog post where I’m using MDX instead of Markdown! I hope I haven’t broken anything 🙃