There are so many best practices and advanced patterns — the problem is keeping track of them and keeping them in mind as you develop new components.
I wrote this checklist to help myself remember all of the things I need to be thinking about as I go through building highly reusable components.
This checklist applies equally to both Vue and React.
Really, it doesn’t matter which framework you use, since the principles of component composition are exactly the same. And the general process for designing a component doesn’t change either.
Condensed checklist at the end of the post.
Avoid speculative generality
This one sounds pretty obvious, but I find myself forgetting this all the time.
You need to fully understand your use case, and only implement that. If you just dive into code without thinking first, you’ll end up writing in a bunch of features that will never be used. That’s a colossal waste of your precious time.
When writing a component that is supposed to be reused across many different use cases, the temptation to provide more and more abstraction is easier to justify.
However, if you write the component well, it should be fairly easy to come back later and add in new use cases you couldn’t anticipate.
This is where 80/20 thinking comes in handy. A few of the features you implement will be responsible for handling most of the use cases. Focus on those.
I’ll leave you with one last tweet about API design:
Simplify the API
Now that we’ve stopped to think, let’s simplify our API as much as possible.
You want to find the perfect balance of working out of the box, and configuring as necessary. On top of that, you want to keep things clear, explicit and approachable. Don’t be too clever or you will piss everyone off.
Too simple and it doesn’t cover enough use cases to be helpful. Too flexible and it becomes too difficult to understand how to do anything.
Just take a look at how many options this jQuery Datepicker has. It’s terrifying!
This is where more advanced composition patterns really come in handy.
Typically you’re making trade offs between simplicity and flexibility. But techniques like render props and compound components allow you to achieve more flexibility while reducing complexity.
Research prior art
We have a pretty good idea of what we we’re building now, but first we need to see what else is out there.
There are a few reasons for this:
First, we can get great ideas for our implementation.
If we look at mature open source projects, they will be more or less feature complete. This lets us double-check our assumptions about what we might need to build out.
We can also see how the API was built, what abstractions were used, and possibly even some implementation details we wouldn’t have considered.
Second, we can potentially save ourselves some work.
There’s a good chance we’ll find something that we can use to speed up development. We may even find something that ticks all our needs, and end up short-circuiting this whole process.
In his Advanced React.js course, Ryan Florence mentions that he likes to check out jQuery plugins. They’ve stood the test of time, and likely have all the features that you’d ever consider putting in a UI element.
A great place to start is in the Awesome repos:
Break up components into bite-sized pieces
Compositional frameworks use components as their main technique of abstraction (vs. objects in OO or functions in functional programming).
If you shove everything into one or two giant components, you aren’t taking advantage of this at all!
Smaller components are better:
- Better abstraction makes your code cleaner, easier to understand, and easier to maintain
- Encourages reusability of the code
- Necessary in order to use more advanced fancy-pants composition techniques
There are a couple main ways to think about splitting up components:
Isolating behaviour from presentation
You have state and logic in one component, with a separate component responsible for rendering. We would typically do this through render props and stateless functional components.
Levels of abstraction
It’s often a good idea to keep the level of abstraction consistent within a component. Either the component would deal with native DOM elements, like
span, or it would deal with framework components.
Use powerful composition patterns — only where needed
Render props, provider pattern, higher order components. These patterns are seductive and fun to use.
But they are not always the right tool for the job. Knowing when to use each one to it’s maximum effect is important. Otherwise you may use the wrong tool for the job and dig yourself into a hole.
If you want to learn more about these, I highly suggest checking out Kent C. Dodd’s article on advanced patterns in React:
Break up parts of your component into smaller bits that can be recombined in different ways.
For example, take your
Table component and break it down into
TableFooter components. Most people will be fine to use the regular
Table, but now someone can build their own table and build a custom header for it without too much trouble.
Higher order components
Take a component and wrap it up inside another component to add functionality to it. This concept evolved from the object-oriented idea of mixins, but has largely replaced it (although Vue still has supports mixins).
When React first came on the scene, mixins gave developers an escape hatch, allowing them to go back to something familiar from the object-oriented world.
After awhile we figured out that composition is far better than inheritance, so higher order components became the norm.
Render props and renderless components
Now we’re seeing another shift. This time away from higher order components, and towards what are called render props (or function as children).
Render props let us delegate rendering to the parent component, giving us even cleaner separation between behaviour and presentation. If you combine this pattern with the prop getters and setters pattern, you can build incredibly flexible and expressive components with very few lines of code.
Controlled and uncontrolled props
Does the component control the state, or does it’s parent control the state? That is the difference between an uncontrolled and a controlled prop.
Generally, uncontrolled props are used since they allow us to encapsulate complexity and state within the component. However, if you want more flexibility — which we need when writing a highly reusable component — you can allow a prop to be controlled by the parent.
If you’re going to expose a prop to be controlled, why not expose the entire state reducer? Then you can have extremely fine-grained control over the behaviour of the component — but you don’t have to re-write any logic.
If you’ve used Redux or VueX or something similar, you probably know this pattern.
Instead of passing props around all day long, and passing them down through many many levels of components, you simply “inject” them where they need to be.
Accessibility is not an afterthought
I’m not very good at accessibility yet.
It’s something I’ve been getting better at recently though, because I believe it’s incredibly important to making the web a better place.
Make it easy to style
How easy is it for consumers to style your component the way they need?
The intention is that your component will be used all over the place. In all sorts of wild and various contexts. It will be used in ways you could never have imagined even in your weirdest dreams.
You need to make sure that other devs can easily style and change the layout of your component without resorting to ugly CSS overrides.
Renderless components make this super simple. You can just delegate all rendering to whoever is using your component.
However, in many cases it’s useful to render something by default. A good approach here is to use BEM in your component and provide CSS classes that can be easily overriden.
But CSS isn’t Turing complete!
Alternatively, you can take the cool-kid approach and use CSS-in-JS (it’s not for everyone).
When this idea started out, most libraries were implemented using inline styles. This caused some performance issues and had some other issues as well.
Nowadays, most libraries work by dynamically attaching stylesheets to the DOM, and the biggest issues have been resolved. I haven’t had a chance to dive into these in awhile, but it’s on my list!
Do you even test, bro?
You don’t want tons of people using your beautiful new component, only to have it flake out on edge cases. Or heaven forbid you add a slick new feature — but it breaks everything else.
When you’re building a reusable component like this you probably won’t do any integration or end-to-end testing. You’ll be writing unit tests that focus on testing tricky logic and making sure the component is rendering properly.
Here are a few great articles on testing React and Vue components if you need to get started:
Documentation — It’s what makes all the difference
“I’ll write the docs later”, you think to yourself as you push the last beautiful crafted commit to Github. “I mean, even if I don’t get to it no one will really care.”
But great documentation is what turns a good component into a great component.
You know the feeling when you’re trying to figure out how to get a library to do something, or you get confused about React’s (or Vue’s) lifecycle methods. If you can’t find the answer quickly, it’s super frustrating.
Sometimes you’ll even get rid of the library because you can’t figure out how to get it to work!
Don’t let your shiny, brand new component to go to waste because of a lack of good documentation.