Hello 👋

I'm Alejandro

A Web Engineer from Málaga, Spain based in London, UK

Avoiding complexity with Compound Components

Over the years, the way we consume and create our React Components have changed considerably. I'm pretty sure, at some point this thought got on your mind when creating a component.

I'm going to create this generic component that we will reuse.

And then as you add features to it, this component become a monster, and it's not generic anymore.

It's really complicated to get it right the first time.

When you create your own component, you need to think about who is going to use it. Whether is your future you, someone from your team, or even a person who you don't even know.

The "props way" of exposing a React Component API is good and it just works, but sometimes it lacks flexibility. In this post, I'm going to explain to you how you can improve the experience of your consumers with "Compound Components".

Imagine we are implementing a Dropdown component, in which we need some options to choose from.

We'll do something like this...

import React from 'react'

function Dropdown({ label, options }) {
  return (
    <div>
      <button>{label}</button>
      <ul>
        {options.map((option, i) => {
          return <li key={i}>{option.title}</li>
        })}
      </ul>
    </div>
  )
}

export default Dropdown

As you can see options are an array of objects, which are defined as follows:

[
  { title: 'Option 1' },
  { title: 'Option 2' },
  { title: 'Option 3' },
  { title: 'Option 4' },
  { title: 'Option 5' },
  ...
]

Now, let's say we are asked to implement a selected option functionality.

In this case, we have 2 options:

  • A selected prop, which will have the selected title and then, find the selected option inside options.
<Dropdown selected="Option 1" />
  • Pass a selected option along with the option object and then, do {option.selected === true && <li class="selected">{option.title}</li>}.
[
  { title: 'Option 1', selected: true },
  { title: 'Option 2' },
  { title: 'Option 3' },
  { title: 'Option 4' },
  { title: 'Option 5' },
  ...
]

And for every single feature you are asked for in your Dropdown component, you will need to make the exact same choice.

What are Compound Components?

The idea of Compound Components is to separate your component in several pieces, letting the complexity live outside the component and also letting the consumer of our component decide what features to implement.

Lets see it in practice.

import React from 'react'

function DropdownOption({ children, ...props }) {
  return <li {...props}>{children}</li>
}

function Dropdown({ label, children }) {
  return (
    <div>
      <button>{label}</button>
      <ul>{children}</ul>
    </div>
  )
}

Dropdown.Option = DropdownOption

export default Dropdown

Notice, we are exposing the Option component along with your Dropdown. Then, we consume the component this way.

<Dropdown label="Select an option...">
  {options.map((option, i) => {
    return (
      <Dropdown.Option
        className={options.selected ? 'selected' : ''}
      >
        {option.title}
      </Dropdown.Option>
  })}
</Dropdown.Option>

With this approach, we keep the Dropdown clean as clean as possible, allowing who consumes it to add features around the options without having to change our component to do so.

Read more about