Hidden in plain site β the levels of component reuse
The path to portability for UI components
Tremendous value is hidden in plain sight. All you need to capitalize on it is a keen eye. In the past, it looked like gold nuggets twinkling in shallow Sierra streams or crude oil seeping out of parched desert soil. Today, value is camouflaged in digital resources βUI components. Frontend teams can accelerate development while saving money by reusing the components theyβve already invested in building. This article illustrates the three levels of reuse found in most apps and shows you how to get the most out of your UI components.
Point of viewΒ layer
A paradigm shift is happening on the frontend. Customers expect a compelling user experience, so frontends have become more expressive but also more complex. Since HTML and CSS alone cannot handle such complexity, frontend engineers have adopted modern view layer technologies like React, Angular, or Vue. Depending on your point of view, UI components can be a byproduct or the end product of frontend development.
Byproduct
The frontend team is tasked with delivering apps and features. In the process, they also build something that has immense value. As a byproduct of using next-generation frontend tools like React they are idiomatically compelled to split UIs into discrete componentsβββmodules that produce HTML for a given set of inputs. One of the hallmark benefits of components is reuse. So in addition to an applicationβs essential value, the organization is also getting reusable components for free. Or should be.
End product
Letβs look at it from a different perspective. Organizations have traditionally been aligned around βappβ teamsβββgroups of people that focus on improving and maintaining one codebase. However, now that weβre all building components, that can (theoretically) be reused across codebases, the team boundaries are blurred. If we look at components as the end product of effort instead of apps, frontend teams are in the business of manufacturing components.
UI component libraries
Forward-thinking organizations are responsible for building and maintaining a component libraryΒ . Teams within those organizations still assemble apps and features [out of components], but individual makers ultimately add their work to the larger component library.
A component library collates UI components from one or more apps so that they can be shared and reused throughout the company. Having a component library as the focal point of the frontend organization yields a few distinct benefits. For engineering, a library accelerates app assembly and saves money. For design, a library ensures UI consistency.
There is no such thing as a free lunch. A component library has its own set of challenges. You might think your components are automatically reusable, but in practice, theyβre trapped inside the apps you build. Many are single-use; theyβre chained to the specific environment of the app (often unintentionally). Most are undocumented; only their creators know how the components are intended to work.
Unlike a real library, the one with books, members canβt just βcheckoutβ components. By using a framework like React youβve already done most of the work to create reusable components, but some polish remains before they can spread throughout your organization. Letβs find out why by examining three component types teams encounter when building their library.
Single-use componentsβββthe mostΒ common
A single-use component canβt be used anywhere other than its current location. Typically, this is a result of heaping multiple concerns together into one component. For example, the CommentList
React component below is responsible for fetching data and rendering HTML.
When concerns converge in one component, reuse is difficult because the surface area and level of specificity to an appβs context grows to the point of being hard to reason about. This also makes a single-use component hard to debug for other developers because they must shoulder unnecessary cognitive burden.
Single-use components are miniature monoliths. Theyβre also the most prevalent component type. As is the case in coding, developers in a hurry or early in a project can miss opportunities to generalize. The pressure to get sh!t done is formidable, but when working with components itβs sensible to consider later reuse. βSeparation of concernsβ is a simple pattern that you can follow to make your components easier to understand and get you on the path to reuse at the same time.
Pattern: separation ofΒ concerns
A common technique for unpacking complexity is to βseparate concernsββββdecompose modules into smaller, more focused submodules that divide in a way that makes them easier to understand.
This pattern has been embraced by component authors in various forms, leading to dichotomies between βpresentational vs. containerβ[1], βpure vs. impureβ, and βstateful vs. statelessβ components. All of these patterns advocate splitting a component into two componentsβββa parent and a childβββwhere the parent (or container) renders the child, and the child is more βfocusedβ or βpureβ.
When designing for reuse, we can follow a similar pattern, by extracting the non-reusable part of the component into a parent component, leaving the βcoreβ of the component as a reusable child component.
Break apart UIs into the smallest possible units that make sense. Separate messy data handling from appearance.
Reasons for single-use βparentβ components
If you separate a componentβs concerns as suggested above, youβll find that a single-use parent component (responsible for interfacing with the app environment) is necessary for building any app. Here are a few scenarios where theyβre useful:
- Putting down a link to another route
- Firing queries or mutations off to a server
- Dispatching or getting data out of a global store
- Rendering any sub-components that do one of the above (reliance on the environment passes up the component hierarchy)
Reusable componentsβββan achievable goal
A reusable component does not rely on anything from the environment it renders in. It renders exclusively based on its inputs and internal state because there is a clear delineation of presentation and data. If the input data provided to a reusable component changes so too does the resultant visual representation.
A reusable component relies on standard input API (props
, scope
, arguments
depending on the view layer) to pass in any data the component needs. It then declaratively reconfigures itself depending on that data. For instance, if you need to render a link pass the URL in as an input. If you need data pass it in as an input. If you need to submit a form or fire a mutation, pass a callback or event handler in as an input.
When there is standardization of inputs and outputs, components can enjoy wide reuse in an app with any data. Not only is the pattern beneficial for reuse, itβs widely adopted best practice for creating components that are simple to test and easy to reason about.
For example
Letβs see how this might work in practice. The single-use CommentList
example earlier showcased a component whose presentation and data requirements were intertwined. To transform CommentList
into a reusable component weβll need to divorce presentation from data. Given the comments
data comes from the environment (the server in this case), weβll split the reusable part from the non-reusable part so that a variety of data can be rendered.
Presentation: what it looks like
Data: what information it displays
Ergonomics increaseΒ reuse
Weβve seen that in order to transition from single-use to reusable components, teams must overcome a technical challenge by separating component concerns. To encourage wide component reuse, teams must face a usability challenge by improving component ergonomics.
Ergonomics are the design characteristics of an object that allow for efficient and safe interaction.
Component reusability can be a double-edged sword. Each input to a component may take a potentially infinite set of values. While greater flexibility supports more situations, it also increases complexity because there are more permutations to design and document. If fellow developers want to reuse your work they must overcome the hopeless task of spotting how a component is intended to be used from seemingly infinite input variations. In order to increase usability make your component easy to understand.
Portable componentsβββthe promisedΒ land
A portable component separates presentation from data, can exist independent of its environment, and documents which states are used by that component. When a component is portable, it is packaged in such a way that other developers can easily use.
The difference between a reusable and portable component is the usage documentation.
State is the intersection of design and frontend development. In design, state is the specification of how a component should look for given inputs (data, viewport, etc.). In development, state describes the specific value of inputs that result in a given appearance.
Documenting state, the expected appearance given typical inputs, is a lightweight way to express how a component is supposed to be used for your design and development compatriots. Itβs a practical compromise between documenting all possible component inputs (more work for the author) and not providing usage examples at all (more work for the consumer). So how do you create a portable component? Letβs find out.
UI component explorers βyour new bestΒ friend
Think of component explorers as a window into UI component states. A component explorer is an application that lives parallel to your production app that showcases your UI components in various test states. In this case, a state is essentially a visual test case or specificationβββa set of typical inputs to that component.
Component explorers help engineers build modular UIβs by visualizing components so that they can be constructed in isolation of app logic. They allow you to simulate component states for testing and serve to index your appβs UI components for later reuse.
Using a component explorer
You would use a component explorer when constructing a portable component to define and test various states. Your team would also use one to βexploreβ the supported states of a component to see how they might use your component. Hereβs how CommentList
would look with test states for GoodGuys
, BadGuys
, and OkGuys
in a component explorer:
Putting it allΒ together
Portability is the ability to browse expected states of a reusable component via a component explorer. Creating reusable components does not alone ensure that theyβll be utilized by others. By expressing a componentβs typical or expected usage via state, we provide a guide for our team to build more consistent UIs and save time.
Learn more about component explorers
UI component explorers βwhat they are
Component-Driven Developmentβhow to use them
Destiny
UI components were always intended for mass consumption. Their conceptual lineage traces back decades to computer science first principles and centuries to the birth of industrial manufacturing.
In this article weβve covered three common patterns teams encounter when building componentized frontends: single-use, reusable, and portable components. Each pattern has its right place. Being vigilant about opportunities to reuse components and designing for portability from the get-go will allow you to capitalize on tremendous value hidden in plain sight.
[1] Note: Dan Abramovβs presentational/container component split does not map one-to-one to the single-use/reusable split Iβm discussing here. While a presentational component is typically well-suited for reuse and a container component is often too intertwined with its environment for reuse, it is sometimes possible to reuse a well-designed container component (especially within the same app). However, the reliance of container on its environment, e.g., the presence of a particular key in the global Redux store, will always constrain the wider shareability of that component.
Code samples inspired by Michael Chanβs gist π