Developers frequently talk about the importance of reusing code. We talk about the DRY principle (Don’t Repeat Yourself), code reviews are rife with comments like: “can you encapsulate this into a function and reuse it on line 19”, and we frequently explain to product owners why it’s important that when we change something over here, it changes over t_here_ automatically_._
But these discussions lack some nuance. They often make it seem like reusability is binary — code is either reusable or not. I’d argue there are actually three levels of reusability.
The first, and least reusable type of component cannot be reused at all. Experienced developers build this type of component under strict, inflexible deadlines. Inexperienced developers build this type of component when they just want something to work or haven’t yet learned about reusing code.
The middle ground is full of components that can only be reused within one application. Most components fall into this camp. Developers care about reusability and want to reuse their components again and again rather than re-inventing the wheel, but don’t want to put the effort into extracting those components and putting them in a library.
The third type of component that can be shared between applications. These components live in libraries like Material UI or React Toolbox. They can be imported into an application and their components can be reused.
Each level of reusability requires a different amount of effort to build, and provides a different level of value to the team and the business. Let’s investigate what a Button
component might look like at each level.
Design credit: Joe Cahill
There are a number of ways to build a button, and different team members probably have different preferences. I’ve seen developers rebuild a simple Button
hundreds of times rather than build a single reusable button that can be reused across their application.
For example, sometimes I see buttons with specific business logic and styling baked-in. I’ve even seen some hard-coded text.
const ComparisonButton = ({isDisabled,icon,width,color,region}) => {const color = isDisabled === true ? '#999999' : '#1274b8';const btnClass = region === 'us' ? 'btn__blue : 'btn__red';
return (<button className={btnClass}><Iconstyles={icon}width={width}color={color}</Icon>Compare Me</button>);};
It can be used like this:
import { ComparisonButton } from './ComparisonButton
const ParentComponent = () => {return <ComparisonButton />}
There are a few major problems with the way this Button
component is implemented.
Button
. What happens if you need to internationalize your application, and the Button
text needs to be in French?Button
knows about the Icon
component. What happens if someone wants to use this Button
without an Icon
in it?3. The Button
’s styling is determined inside of it, rather than passed in as a prop. This makes the styles more difficult to override. What if you want to reuse the logic of the Button
with slightly different styles?
This Button
is really specific to its context. It can only be used in one scenario, even if it shares logic with all of the other buttons on the site. You can’t even pass whatever text you want into it!
Let’s say we wanted to refactor this into a component that could be reused within our application.
If we refactor the previous example to be reusable within our application, it needs to be much more generic.
const Button = ({ children, onClick, className, ...props }) => {return (<button className={className} onClick={onClick}>{children}</button>);};
It can be used like this:
const ParentComponent = (props) => {
const color = props.isDisabled === true ? '#999999' : '#1274b8';const btnClass = props.region === 'us' ? 'btn__blue : 'btn__red';const onClick = () => { // do something }
<Button className={btnClass} onClick={onClick}><Iconstyles={props.icon}width={props.width}color={props.color}</Icon>Compare Me</Button>}
Now, Button
doesn’t need to know about Icon
. It’s very easy for another developer to reuse the Button
in a scenario where the Icon
isn’t present.
We’ve also moved the knowledge of what color the Icon
should be when the button is disabled into the ParentComponent
. Additionally, the ParentComponent
manages how the Button
should be styled based on region.
The logic inside of Button
only deals with what it needs to know, while the application handles the business logic. That allows other developers working within the same repo as this app to reuse the button.
Say your team expands, and you want all of the teams to provide a consistent experience to your users. You’re going to need to level-up to the third type of reusable component. You’ll build a library of reusable components that can be consumed by all of the teams you work with.
That library:
Your button will probably still look like it did before, but now, you’ll be importing it from your library, rather than from another file in your application.
How to actually build a reusable component library is a topic for another post, but if you’re trying to decide whether you’d like to take this project on, consider reading Should You Build a Reusable Component Library?
There are merits and downfalls to all three levels of reusability. Sometimes, you just need to finish a feature. Other times, you want to share components across your team.
When you’re developing a feature, you should choose the appropriate level of reusability for what you’re working on, the phase of your company, and your team’s bandwidth. Sometimes it’s totally acceptable to build something on the lowest rung of the reusability ladder. Other teams might need a more robust solution. Just always make sure you consciously choose a level of reusability and recognize the assumptions you are making.
If you enjoyed this post and are interested in learning more about reusability and reusable component libraries, you might consider:
🌟 If you liked this post, make sure to follow me on medium, follow me on twitter, and support me on Patreon**!**🌟