Configuration vs composition
Tags: javascript, webdev, architecture
Published: 2020-05-14 00:42:12

---
title: Configuration vs composition
canonical_url: https://www.rmkennedy.com/posts/config-vs-compose
description: Writing an application in building blocks offers some advantages.
---

> This post was originally published on [rmkennedy.com](https://www.rmkennedy.com/posts/config-vs-compose).

When designing a complex, long-lived component, there's one tradeoff that I think about constantly. Do I want to _configure_ my component, passing in a large object with options? Or do I want to build subcomponents, which can then be _composed_ into the final interface?

## What is the tradeoff?

I'll illustrate with two possible APIs for a React dropdown component. The first implementation uses a "configured" interface. All options for the component are expressed as data passed to a prop.

```js
<ConfiguredDropdown
  value="lab"
  items={[
    {
      displayName: 'Labrador Retriever',
      value: 'lab',
    },
    {
      displayName: 'Pit Bull Terrier',
      value: 'pit-bull',
    },
    {
      displayName: 'Boston Terrier',
      value: 'boston-terrier',
    },
  ]}
/>
```

This gives you a single prop into which you pass your data. There are a few ways to do this, but depending on my data source `myItems` I might write something like this:

```js
<ConfiguredDropdown
  items={myItems.map((item) => ({ displayName: item.name, value: item.id }))}
/>
```

Implementation of the `ConfiguredDropdown` component is fairly straightforward -- iterate over each value in `items` and render the corresponding menu item. Done.

However, once your component makes it out into the "real world", the assumptions you made initially could break down. What if you want `onClick` and `onHover` handlers? Additional CSS for some menu items? Submenus? It's certainly possible to add new features to your component for each use case, and in some cases this may work well. However, there's an alternative implementation that could save you some effort.

Consider instead a "composed" interface. Instead of options, the dropdown items are conceptualized as content, and accordingly are passed as React children.

```js
<Dropdown value="lab">
  <DropdownItem displayName="Labrador Retriever" value="lab" />
  <DropdownItem displayName="Pit Bull Terrier" value="pit-bull" />
  <DropdownItem displayName="Boston Terrier" value="boston-terrier" />
</Dropdown>
```

Now, your data items are passed as children instead of as a data prop. The code for passing in a custom data source `myItems` hasn't changed much:

```js
<Dropdown>
  {myItems.map((item) => (
    <DropdownItem key={item.id} displayName={item.name} value={item.id} />
  ))}
</Dropdown>
```

The benefit here is that, as long as you pass unrecognized props to the underlying DOM element, you no longer need to anticipate your user's needs. `onClick` and `onHover`? Passed through. Custom CSS? Both `style` and `className` are available. Submenus? Possible to build using a combination of the other properties.

> (aside) There's a side benefit, too; when each item is a subcomponent, each item gets the benefits of React's prop diffing.
>
> With the configured component, the whole dropdown would re-render if you changed one item; with the composed component, React would only rerender the item that changed. This wouldn't matter in a small example like this, but on larger, more complex components you may see a performance boost.

## A real-world example

My favorite example of this type of tradeoff is [Handsontable](https://handsontable.com/) vs [react-virtualized](https://bvaughn.github.io/react-virtualized/).

Handsontable is an amazing library. I've used it professionally, and it has options for most things you might want to achieve with a spreadsheet in the browser. Most... but not all. Between 2017 and 2019 I was on a project to build a search application for biological data, and due to the complexity of the results display I chose Handsontable. It worked well for a while, but eventually I found myself needing features outside its customizable parameters.

If I had to write that project again, I'd reach for `react-virtualized`. This library offers primitives &mdash; `AutoSizer`, `Grid`, and `ScrollSync`, to name a few &mdash; that you compose to meet your application's needs. Granted, `react-virtualized` does not offer spreadsheet capabilities like Handsontable, but I could imagine an alternate API for a spreadsheet component with more composability.

## Creating your own composed APIs

Here are some tactics you can use in your own codebase.

### Use Context, Providers, or Services to your advantage

Complex applications often contain multiple components that must work together in sync. In my experience, unless the developer pays attention to the design, this tends to generate ["God components"](https://en.wikipedia.org/wiki/God_object) that render everything and manage state. These components are tightly coupled and require effort to maintain.

Many front-end frameworks offer mechanisms for synchronizing multiple components. React offers [Context](https://reactjs.org/docs/context.html), Vue has [`provide` / `inject`](https://vuejs.org/v2/api/#provide-inject), Angular has [services](https://angular.io/guide/architecture-services), and Svelte provides [`getContext` / `setContext`](https://svelte.dev/tutorial/context-api).

If you have a special case to handle, or if you're not using one of the above frameworks, don't be afraid to use a singleton. As long as state changes are reported correctly, and you observe the rules of your chosen view layer (e.g. never mutate an object passed as a React prop) then a singleton could dramatically simplify your implementation.

### Functions are the best configuration

Sometimes, your library truly does require configuration. Traditionally, this is done with a configuration object with a specific documented API. However, you may want to consider accepting functions as configuration. By passing responsibility to the user, you are not only writing less code, but also adding greater flexibility to your API. [React render props](https://reactjs.org/docs/render-props.html) are a great method for allowing consumers to customize a component, and my favorite example of a render prop API is [Downshift](https://www.npmjs.com/package/downshift).

### Exposing a composed API as a configurable one

If you want to simplify certain use cases, it doesn't take much code to wrap a composable component and present it as a configurable component instead. Here's a suggestion of how I might implement `ConfiguredDropdown` based on the `Dropdown` and `DropdownItem` components above:

```js
// Since this is just an illustration, I'm not going to do anything special with `value`.
export function ConfiguredDropdown({ items }) {
  return (
    <Dropdown>
      {items.map(({ displayName, value }) => (
        <DropdownItem key={value} displayName={displayName} value={value} />
      ))}
    </Dropdown>
  );
}
```

This is useful when you want to offer a "base" version of your component that covers 80% of the use cases, but also add an "advanced" version where users can compose to get the functionality they want.

## But as always: use your own judgement

Some circumstances &mdash; performance goals, interoperability, other constraints &mdash; may change the calculus, and you may find it a better fit to design a configured rather than composed component. In small cases, you may not even notice the difference. You are the architect of your own apps, so while you should keep these points in mind, decide for yourself on a case-by-case basis which approach you want to take.

## References

- Ryan Florence, ["Compound Components"](https://www.youtube.com/watch?v=hEGg-3pIHlE) (YouTube)
- Kent C. Dodds, ["How to use React Context effectively"](https://kentcdodds.com/blog/how-to-use-react-context-effectively)
- Svelte Tutorial, ["Context API"](https://svelte.dev/tutorial/context-api)