Compose React Components with a Blender

05-11-22 Reed Spool

Composing solid, flexible React components is like making a smoothie: what you get depends on what you put inside. Learn techniques for composing React components using children, render props, component composition, and more.

How do we make React components that fit together both now and in the future? A good answer can prevent a lot of rework and heartache. Maybe you’ve already made solid components that meet your project’s needs today. But when those needs change, you might find yourself throwing out good work or spelunking on a giant refactoring mission! Some investigation might yield dividends.

Composition is the fancy word for “how stuff fits together.” Composition is such a big topic in the fields of Software Engineering and Computer Science that it has two extensive Wikipedia entries: Object Composition and Function Composition. As if that were not enough, the entire subfield of Design Patterns and the famous, big book of the same name are all about techniques of composition. However, this article explores specific techniques of composition with React components, not a scientific analysis of the whole field. A little intuition is enough for our purpose.

To build an intuition for composition, think of a blender. The blender itself does just one thing: it spins blades inside a container. What comes out of the blender depends on what you put inside. Fill it with diverse ingredients, and you can make smoothies, soups, salsas, sorbets, and so much more. This year marks 100 years since the blender’s first patent, and it remains a staple for kitchens from small homes to big restaurants. Surely such a successful, simple machine has a lesson or two for us.

So, can we make React components as robust and flexible as blenders? Can we make a component that does its job well and works together with a diverse set of other components? Let’s look at some techniques for how React components can compose.

Children

In React, children are unavoidable–and not very flexible. The children prop is the default built-in React technique for composing components. To use the prop, simply nest the components in their JSX form.

// Definitions
const ParentComponent = 
  ({ children }) => <div>{children} Smoothie!</div>
const ChildComponent = () => <span>Banana</span>;

// Usage
export default function App() {
  return (
    <ParentComponent>
      <ChildComponent />
    </ParentComponent>;
}

The above example code produces this:

Banana Smoothie!

This might seem too simple to satisfy the lofty ideals of composition, but it does! All you need is two components to fit together. We could even replace our child with a new component to meet new requirements. Maintainable code is rarely fancy.

To paraphrase someone important in my upbringing, “Kids. Can’t please ‘em. Can’t fit ‘em in a blender.” Children don’t blend. Our parent component only makes smoothies. If we replaced our “Banana” child component with a “Sourdough” one, it would not make sense. Please do not put bread in a blender, then tell me it’s a smoothie!

Of course, you could copy and paste the parent component and change “Smoothie” to “Breadcrumbs,” but that is the rework we want to avoid. When we use React’s children prop, we cannot modify the children. We either put it somewhere, or we exclude it entirely. A blender does not simply place tomato, onion, and chiles into a div; it transforms them into salsa!

On top of non-blendability, the children prop has another major drawback: you have only one to work with per component. To take a break from the kitchen metaphor, imagine that you want to compose a “meeting” component with both “title” and “date” components. If you want to put those two components in separate places, then the children prop is not the right choice. The forthcoming techniques have no such restriction.

Benefits of children

  • Simple to use
  • Built-in and official
  • Makes JSX look like normal HTML
  • Easy to read and comprehend

Limitations of children

  • Can’t do anything fun with the prop except put it in the right place
  • Can’t have more than one
  • Can’t fit ‘em in a blender

Component Instances

A component instance is the value of a JSX fragment. For example, <span>Me</span>. When you write a functional component and return some JSX, you are returning a component instance.

Because a component instance is a JavaScript value, we can pass one as a prop:

// Definitions
const InstancePropComponent = 
  ({ content }) => <div>{content} Smoothie!</div>;
// Same as Children example
const ChildComponent = () => <span>Peach</span>;

// Usage
export default function App() {
  return <InstancePropComponent content={<ChildComponent />} />;
}

The output of this code is similar to the children example:

Peach Smoothie!

The component’s code looks a lot like the children prop example, but the name of the prop changed. Unlike children, the content prop is not built-in; the name “content” is our choice. We are allowed as many such props as we like, and we can put them in separate places.

Because the component instance is evaluated before it is passed as a prop, we have no way to modify it in the parent component. Component instances do not blend. A blender does not simply place butternut squash, garlic, and vegetable broth into separate divs; it transforms them into soup!

Benefits of Component Instances

  • Simple to use
  • Can have many and use them separately

Limitations of Component Instances

  • Can’t do anything fun with the prop except put it in the right place
  • It doesn’t blend

Render Props

The React team gave the name “Render Props” to a specific technique for composing components. The most basic use looks like this:

// Definitions
const NullaryRPComponent = ({ fnContent }) => (
  <div>{fnContent()} Smoothie!</div>
);

// Usage
export default function App() {
  return <NullaryRPComponent 
    fnContent={() => <span>Strawberry</span>} />;
}

Running that produces the following—which you might expect by now:

Strawberry Smoothie!

Why would we use this? Why not simply use children or pass a component instance? If this was all you were doing, those are likely better options. But we can go further.

We can add parameters to the function we pass. The above example had no parameters, hence the name “nullary”. Let’s try one parameter:

// Definitions
const UnaryRPComponent = ({ fnContent }) => (
  <div>{fnContent(“Frozen”)} Smoothie!</div>
);

// Usage
export default function App() {
  return <UnaryRPComponent 
    fnContent={(state) => <span>{state} Blueberry</span>} />;
}

Which produces:

Frozen Blueberry Smoothie!

With this component, we finally have some superpowers. The UnaryRPComponent transforms the state of its Render Prop with a parameter. This does not seem so special yet. We could have used a “Frozen Blueberry” component in our previous examples and gotten the same result. How about a more complex example?

// Definitions
const PropsRPComponent = ({ fnContent }) => (
  <p>
    {
      fnContent({
        style: { textTransform: ‘uppercase’ },
        onClick: () => alert(‘Such Wow! Much Health!’)
      })
    }
    {‘ ‘}  
    Smoothie!
  </p>
);

// Usage
export default function App() {
  return (
    <PropsRPComponent 
      fnContent={(props) => <span {...props}>Kale</span>}
    />
  );
}

Finally, we blend:

KALE Smoothie!

As if by magic, the parent component transformed the content of the Render Prop in two ways. First, the text in the Render Prop is now uppercase. Second, the Render Prop now speaks Doge when clicked.

If the {...props} syntax blends your brain, do not be alarmed, it is confusing. The result is the same as if we had written the props explicitly, like so: <span style={{...}} onClick={() => {...}>Kale</span>. This technique is officially named Spread Attributes but is commonly called “Rest Props.” It combines the power of JavaScript’s Spread syntax with JSX.

Unfortunately, Render Props require ugly function calls in the middle of otherwise clean JSX. This feels like chopping something up before putting it in a blender. Surely there is a better way?

I have one more petty gripe with Render Props: Why is this specific technique given this name? When we pass component instances or children, we’re also rendering a prop. Unfortunately, if we call those “render props,” we may confuse some people.

I know you’re as hungry as I am, but we’ve got one more technique to explore.

Benefits of Render Props

  • More control than children or component instances
  • Can run arbitrary code in the passed function
  • Can have many and use them separately
  • It blends!

Limitations of Render Props

  • Must be called as a function, mismatched with JSX
  • Vague name for a specific technique

Components

Render Props do not look clean, especially when we want to pass in complex parameters. Passing in a function when we want to provide a component is a bit of a square-peg/round-hole situation. Fortunately, the solution is right in front of us.

All we need to do is capitalize the first letter of the parameter name. Seriously. To use JSX instead of function call syntax is as easy as pressing the “Pulse” button on a blender three times.

// Definitions
const Smoothie = ({ Ingredient }) => (
  <p>
    <Ingredient
        style={{ textTransform: ‘uppercase’ }}
        onClick={() => alert(‘Such Wow! Much Health!’)}
      />
    {‘ ‘}
      Smoothie!
  </p>
);
const Kale = (props) => <span {...props}>Kale</span>

// Usage
export default function App() {
  return <Smoothie Ingredient={Kale} />
}

The result is the same as the previous example:

KALE Smoothie!

I said you only need to do one thing, but this looks very different from the previous example. I admit I cheated. I might as well have added a handful of Oreos to an all-natural smoothie. Let me break down each change:

  1. I changed the parent component’s name to “Smoothie.”
  2. I changed the prop’s name to “Ingredient” (note the capital “I”).
  3. I changed the function call syntax to JSX, with two props instead of one object parameter.
  4. I extracted the passed function unmodified to a component named “Kale.”

After making those changes the result is a far cleaner JSX solution.

Why does this work? A React Function Component is just a function that takes props as an object parameter and returns JSX (a component instance). JSX calls that function behind the scenes, and voila. For more detail on why the prop must be capitalized, see this entry in “JSX in Depth”.

Is this technique still considered “Render Props”? I don’t know. We are no longer explicitly calling a function; JSX does that for us. The only special thing about props is their mapping to JSX.

So, what’s nice about passing components? We get all the transformative power of Render Props with less syntax than passing component instances. On top of that, our parent component can use normal JSX.

The only limitation I can think of at the time of writing is that passing components does not seem to be common practice. That means you may confuse some people with your code. If you can think of other limitations of this technique please let me know!

Benefits of Components

  • As much control as render props
  • Can run arbitrary code in the passed component
  • Can have many, and use them separately
  • It still blends!

Limitations of Components

  • Uncommon technique

Conclusion

How do we make React components which fit together well? There is no one right way, but there are better and worse choices. If we are not conscious of our choices, our code may end up tangled and incomprehensible. I hope this exploration has equipped you with some concrete tools for composing components and a better intuition for your future choices.

Related Content

Check out the 2022 Design Systems Survey!

Over 200 web professionals responded with their design system experiences. Let's learn from this year's findings.

More Details