Skip to main content

Component Driven Development

Save money and time building user interfaces from tested modular components

By referencing a Design System, CDD enables designers and developers to collaborate more efficiently.

Components are standardised, interchangeable building blocks that encapsulate the appearance and function of UI pieces.

Think LEGO bricks. LEGOs can be used to build everything from castles to spaceships; components can be taken apart and used to create new features.

Component Checklist

  • Always name your React components
  • Write consistently organized components
  • Separate constants and helpers into different files
  • Destructure props
  • Don't pass a ton of props in
  • Avoid nesting render functions
  • Conditional rendering practices
  • Assign default props when destructuring

Component Types

Components generally fit into three distinct classes:

  1. Presentational
  2. Structural (Composite)
  3. Stateful

These three categories are a discovered pattern rather than an imposed one — components are not created to fit these categories, but components organically show these characteristics.

Other terms

  • Dumb vs Smart Components
  • Presentational vs Container Components
  • Pure vs Stateful Components

Presentational

Emerge quickly when we build component libraries, as typically all components in a component library can be understood to be built to be reusable and purely presentational, agnostic to what API or application state they're rendered with. They may abstract our styles and theming, concern themselves more with our users' interactions with the app, and provide a thin API layer to be used in other components.

Structural

Whenever we think of "screens," "views," or "pages." These components emerge as we compose presentational components into a structure that represents our app's views and will often distribute our backend's data throughout the app. They compose presentational components and are hence a mapping of our business logic and application state and data.

Stateful

Every app has places where it integrates backend data and business logic to feed into views, generally understood as state. This state is different from our presentational components' local state.

Design Process

  1. Research and planning
  2. Component library
  3. Write providers and hooks for your backend data fetching
  4. Split app into views and pages

Axis of Reusability

This describes a spectrum of our components instead by how reusable they are.

On one side we find our least reusable components, which we'd previously call stateful components. Stateful logic fetches API data, maintains global application state, and maintains business logic. This logic is the "most specific" part of our application as it's unique to the application we're building. While their logic may be further subdivided and abstracted (for instance with Hooks or Redux) stateful components are purpose-built and only show up once.

On the opposite side of this axis is where we find our most reusable components. The hallmarks of these components are that they're presentational and could be used in multiple parts of the app or even other apps. If we're working with a component library then the components in it will certainly be reusable.

Some components are towards the middle of this axis as they'll be specific enough to accept some state or API data and pass it on to presentational components, but may also be used in several places as they aren't responsible for retrieving state or data themselves — hence they're the structural components.

A few components may also have "mixed responsibilities," which happens when components are yet to be split up and will be anywhere along this axis.

If we were to place an app's components onto this axis, chances are that we find less reusable components at the top of the component tree, while towards the bottom we find more reusable components. This happens naturally due to how components are composed. Because stateful components are by definition wrapping larger parts of our application they're at the top. Corollary presentational components are at the bottom as they'd only render other presentational components.

Identifying Bad Components

Components built from poorly chosen constraints. Often labeled hard to understand or technical debt

Too many responsibilities — in other words, it occupies a breadth of the component axis that is too wide, by incorporating both structural and presentational responsibilities, or stateful and structural responsibilities.

Poorly split — for instance, a presentational component may have not been written to be less specific and instead isn't truly reusable. Two components that are used together may share too much information about one another, which may make both harder to read and reuse.

Poorly nested - A component may be "surrounded" incorrectly. This may happen when a presentational component renders a structural or stateful one, which complicates the relationships of all components in this path and forces us to reach for React's Context API too early for the sake of avoiding prop drilling in places where it isn't appropriate anyway.

Multiple signs lead us to believe that a component is bad. They may be too specific and tend to break if you remove them from their intended place, or they aren't as reusable as intended. On the other hand, some may be too generalized, which hinders comprehensibility and decreases their chance of being reused overall — a common issue with render props, which at its extreme appears reminiscent of Node.js' callback hell.

Some components may or may not have an optimal interface or API. As props are our components' inputs, they are what makes components composable, which influences how and if they'll be reused. A component with a suboptimal interface will also lead to poor code in its parent components.

As we can see, there's a wide variety of reasons a component could be bad. However, oftentimes components simply become more complex, which is one of the hardest problems to rectify. Complex components often build up a large amount of dependencies and are discovered once we start testing them.

These are all subjective and vague factors. But, we can use a more nuanced definition of complexity and combine it with our understanding of classes of components to arrive at more actionable problem statements.

Problems that the "axis of reusability" helps us identify Complexity is a factor of information scale (how much information), information diversity (how many elements), and information connectedness (how many cross-relationships between elements).

The component categories we've identified help us understand which components have overstepped an appropriate level of complexity by looking at how wide of a space they occupy on the axis of reusability. Complexity in this case it not only determined by how many pieces of information the individual component handles, but also grows exponentially if a given component falls into multiple categories, for instance if a component is structural and stateful at the same time.

Assuming that complexity arises from three factors, we can also derive that there are three general problems that we can identify "bad components" as being afflicted by:

A component may have too many responsibilities — in other words, it occupies a breadth of the component axis that is too wide, by incorporating both structural and presentational responsibilities, or stateful and structural responsibilities. A set of components may have been split awkwardly — for instance, a presentational component may have not been written to be less specific and instead isn't truly reusable. Two components that are used together may share too much information about one another, which may make both harder to read and reuse. A component may be "surrounded" by others that are in a skewed place of the axis in the component tree. This may happen when a presentational component renders a structural or stateful one, which complicates the relationships of all components in this path and forces us to reach for React's Context API too early for the sake of avoiding prop drilling in places where it isn't appropriate anyway.

The categories and hierarchies of components in componentized apps often matter more than the individual implementation details of a component. How components interact with each other combined with what we require them to do directly dictates how they'll be implemented, which gives us a new tool to identify bad components.

tip

Every piece of UI is now a component, including pages. The only difference is how they consume data.

Resources

URLNotes
Test Component Interactions with Storybook#testing Review
Crafting Component LibrariesTodo
Component Driven
Component Driven Development with Next
Lessons learned building a Typescript Component Library
A Guide to Component Driven Development
APIs for building flexible components
Storybook Figma Plugin#storybook Copy