How abstraction helps you get the most out of UI components
Reuse UIs by simplifying your components
Software maintenance consumes between 40–80% of a project’s resources. The burden of maintenance affects both project velocity and team satisfaction. Wouldn’t you rather create new features than dredge through bugs and tech debt?
Module reuse classically lessens the cost of maintenance. The more you reuse the code you’ve already built & tested the more robust your applications. When building user interfaces, it’s consistent and faster to reuse UI components.
However, components aren’t reusable by default. A core strategy to create reusable components is abstraction. That is, “separating concerns” of an idea from the context in which it was born.
An idiomatic pattern, the “presentational/container” split abstracts the presentational part of a component out of its data context. The goal being a UI component whose code is simpler for other developers to understand.
However, to reuse a component, we also need to make it easier for a other apps to “understand” — ie. consume. We need to abstract the use of a component from the app in which it was born.
Types of components
All components are not equal and not all components belong in a component library. In a previous article I described the difference between single-use and reusable components:
- Single-use components are confined to their current location. They intertwine app-specific concerns with their “core” concerns.
- Reusable components can be applied in many situations. They do not rely on anything from the environment they render in. There is a clear delineation between presentation and data (or anything else that is environment-specific).
The defining characteristic of a reusable component is the ability to render in any environment, given the correct inputs. In other words it’s abstracted from the global state, styles, business logic, and data. This allows for “presentational” or pure components to be reusable in different places within the same app and opens the door for portability to other apps.
This isn’t to say writing single-use components is bad; components that understand the app they are in are the glue that makes the whole thing work. Still, abstracting reusable components out where possible will help grow your library and encourage consistency.
What to do about abstraction
UI component libraries should only contain reusable components that work in isolation of the app context. These are generally pure + presentational UI components that are abstracted from the data context and app environment.
Example: CommentList component
CommentList
renders messages from various authors. Shown below are 3 permutations that rely on the same Star Wars data source. You might find a component like this confined to a galactic chat app. We want this component to be reusable in any app. Let’s make it so.
First, look at a non-reusable implementation of the component:
The component is non-reusable because it’s intertwined with database queries and assumes the presence of specific routes.
If you’ve created this component in haste you might be tempted to pass fields directly from the database through to the component (our commentText
and characterName|Avatar|Id
fields). Although initially convenient, this makes the component awkward to use in other contexts. A better idea is to transform the data in your own app and make the component’s API cleaner.
The component also prescribes how the route to the author should be constructed with <a href=`/character/${id}`>{name}</a>
which requires developers to pass id
as a property. This is poor for reuse because the assumption that an app’s environment is setup with a /characters
directory and routes are constructed with id
’s.
This works in instances where you have you database setup just so and your comments are limited to Star Wars characters
. However, there isn’t much flexibility for situations where you want to display comments from superheroes
or Twitter
users.
A reusable implementation relies only on inputs as props
to pass in data the component requires to render. It does not rely on a specific database fields or assume anything about app routes.
Your component is now ready for different data:
Final words
Abstraction is simplification. Not only is it best practice for a tidy frontend, it allows the flexibility required for component reuse. As you can see with the CommentList
example, small tweaks make a big difference in reusability. It’s not hard to imagine a library consisting of Dropdown
, DatePicker
, and Drawer
components whose patterns are endlessly reused to reduce the maintenance burden on your team.
Chroma is a big proponent of component reuse. No one should have to rebuild the same UIs. We’re exploring a world where frontends are assembled from reusable components and are excited about our findings. If that sounds interesting to you, sign up for the mailing list to discover more articles on the topic. Do us a favor and ❤ this article.