As a Front-end developer, building resilient and reusable components is a top priority of mine, and there are so many arguments in favor of well thought out components.
Thankfully, most of today's UI libraries and frameworks go a long way to help you build the best components possible for your projects (and more importantly, for your team). Nevertheless, keeping a few guidelines in mind can help us avoid pitfalls, especially when it comes to large-scale applications.
In this article, we'll go through concepts I follow everyday that are library and framework agnostic, meaning they apply to UI components as a whole.
- Have a modular approach
- Name your components well
- Keep your props simple
- Keep your business logic in container components
Have a modular approach
Ideally, your components should follow the FIRST principle:
- Focused: one component, one responsibility, if a component is doing too much, ask yourself if you can extract that logic somewhere.
- Independent: ideally, a component should not depend on another one to function. Passing simple and straight to the point props can help you create independent elements. If you've ever used Storybook, think of it that way: Can I extract this component into a story easily?.
- Reusable: UI components are lego bricks, they should fit anywhere pretty easily. Once again, a component's reusability is often determined by the simplicity of its props (more on that topic later).
- Small: I was horrified to see components reaching the 1000 lines mark on a project I'm currently consulting on. Keep ๐ them ๐ small. A small component can be read and explained easily and is simpler to test.
- Testable: How much mocking is required to test this component? is usually a good question to ask yourself, complex components will require a complex context to mock beforehand. Keeping in mind that the easiest components to test are known as pure components, meaning that for a given input, the component will always render the same output, produces no side effects and relies on no external mutable states.
Of course, you'll be working on elements that are truly dependant on your business logic, meaning you probably won't be able to follow these guidelines completely, and that's okay. Some components aren't meant to be reusable and some components won't be independent; but keeping this principle in mind is a good start.
Name your components well
I tend to try and keep my component names short, meaningful and easy to pronounce.
Here are some good and bad examples:
<!-- Good -->
<user-button></user-button>
<payment-details></payment-details>
<user-card></user-card>
<!-- Bad -->
<user-btn></user-btn> <!-- hard to pronounce -->
<user-guarantee-payment-tab></user-guarantee-payment-tab> <!-- too long -->
<ui-dropdown></ui-dropdown> <!-- every component is a UI element, no need to mention it -->
Keep your props simple
As mentioned in the first tip, props can make or break a component.
- Avoid passing down complex object structures, favour individual attributes as props whenever possible
- Use simple names for your props. We should be able to understand its purpose (even partially) upon reading it
Basically, try using Javascript primitives (strings, numbers, booleans) and functions as props whenever possible.
Keep your business logic in container components
Container components (such as layouts) should take care of computation and business logic as a whole in order to pass its results as props to presentational components.
This pattern isn't always valid, but the idea it promotes is important to keep in mind: complex and stateful logic is easier to maintain when kept separate. In the case of a React application for example, this business logic can be extracted in a custom hook, so this "smart/dumb" component pattern really is about separation of concern.
Often times, having each component handle their own logic can lead to them being hard to re-use throughout your application as they will be bound to a specific context.
Don't overdo it
These are just general tips for building efficient components. Of course, each project has different requirements and may not allow for you to follow these guidelines all the time.
As Dan Abramov says in his Writing Resilient Components article: Donโt Get Distracted by Imaginary Problems. Keep in mind that it's not worth it to over-engineer all of your components and to enforce rules that may not bring meaningful differences.
I hope this short list will help some of you build better UI components in your day-to-day. As always, if you have any questions, tweet at me @christo_kade โค๏ธ
Top comments (7)
Component names should be multi-worded to help avoid conflicts with current and future HTML elements. Your examples are but it isn't stated anywhere. Obviously, you wouldn't use
<button>
but you should also not use<modal>
in case that makes it into the HTML spec some day.I waffle a lot about whether or not to pass full objects as props. On the one hand it is very convenient on the other hand it is less explicit about what the child is expecting to use.
I wholeheartedly agree Drew.
I've had trouble understanding code bases where complex object structures are passed as props, I really think it's about losing less time on refactoring & having newcomers grasp a prop's role faster.
e.g. for the modular approach, the ideas of Atomic Design helps me alot.
bradfrost.com/blog/post/atomic-web...
and BEM for styling (CSS) this different building blocks.
css-tricks.com/bem-101/
e.g. a Button Atom
Edit:
in my last project I used often the data attribute instead BEM modifiers.
like
Data attributes are often easier to set/modify with javascript than CSS classes. Additional they are an interesting solution to manage component state in the DOM.
Agreed, depending on the project (especially when it comes to personal projects), I even structure it based on Atomic Design, it really helps !
Great article, and I agree. As an Angular and React dev I wonder if those are what you're referring to here. Can this sort of thing be done with plain JS, or were you implying the use of a component library or app framework?
These tips can totally be applied to web components or to anything of that sort. It's really more about the concept than anything else.
I'm glad you enjoyed the article ๐
Thanks for the great tips Christopher!
I have an architectural question: currently I am building a Vue application which has a container element (OrderForm) and several child elements: e.g. DrinksSelector, FoodSelector and PriceEstimate.
When you select something in the FoodSelector, the PriceEstimate component should update its price estimate. Similar for DrinksSelector.
I'm using VueX for state management, so there's a single source of truth.
According to your article (if I understand correctly) you advise to keep these Food and DrinkSelector 'dumb' and put business logic into the container OrderForm.
In my opinion, you are creating a coupled link between your container and its children. In order to avoid this, what I do is make the children 'smart' by letting them dispatch VueX actions directly and render from the VueX store (instead of its parent's props).
The biggest benefit is that you avoid placing business logic into your containers but move this to your VueX store and its actions. A component thus truly becomes a 'view' and the VueX actions the 'controller'. Components get ridiculously small and easy to understand this way.
This is an approach I'm taking for a new project so it's definitely not battle-tested. What are your thoughts on moving business logic from containers into the store?
Thanks!