How to pass data to props.children

React makes it easy to pass children to reusable components. But what if those children need to receive data from the component that renders them?

17th October, 2018

One of React’s most useful features is the ability for components to receive and render child elements. It makes it ridiculously easy to create reusable components. Just wrap props.children with some markup or behavior, and presto.

constPanel=(props)=><divclassName="Panel">{props.children}</div>

There is, however, one major limitation to this pattern: props.children must be rendered as-is. What if you want the component to pass its own props to those children?

For example, what if you’re building a <Link> component, and you want its children to have access to an active prop that indicates whether the link points to the current page?

One of the neat things about JSX is that its elements are just JavaScript objects. This means that a JSX element’s props and children can be anything that you could place in a JavaScript variable — they can be strings, other element objects, or even functions.

The simplest way to use a function as an element’s children is to interpolate an arrow function with JSX. This pattern is often called passing a render function. Here’s an example:

In the above example, the <Link> component will receive a children function that expects to be called with a single argument: isActive. The component can then call the function whenever the user navigates, and then return the result from its own render() function.

Here’s an example of what this might look like if the <Link> element receives the window’s current location via context (which itself uses a render function):

It can also be called...

The term render function is also used when passing a function to the render prop — as opposed to the children prop.

This pattern is also sometimes referred to as a render prop.

Render functions are typically used when the children that are passed to a component may utilize some state that is contained within that component. But render functions aren’t always the perfect solution. In cases where you need to squeeze every last drop of performance out of a component, or when a function just feels wrong, then cloning can come in handy.

While it’s certainly possible to pass a function as an element’s children prop, it’s far more common to pass JSX elements. And given that JSX elements are actually plain old JavaScript objects, it follows that the component that receives those elements should be able to pass in data by updating props.

Unfortunately, attempting to set an element’s props will cause an error.

It turns out that element objects are immutable objects. Once they’ve been created, you can’t change them. But you can clone them — and merge in updated props in the process.

To clone an element, you’ll need to use React’s cloneElement() function.

React.cloneElement(element, props,...children)

The cloneElement() function returns a new element object with the same type and props as its first argument, but with the specified props and children merged over the top.

For example, here’s how you’d use it to add a className prop to an existing element.

import React from'react'// Create an element object and assign it to `element`let element =<div>Hello, world!</div>// Create a clone of the element, adding in a new `className` prop// in the process.let elementWithClassName =
React.cloneElement(element,{ className:"active"})// The new element has a `className`!
console.log("new className", elementWithClassName.props.className)

If you’re comfortable with React’s createElement() function, then cloneElement() will feel familiar — the only difference is that it accepts an element in place of a type. Of course, given that you won’t use cloneElement() all that often, you might want some help remembering it. And that’s what my printable React cheatsheet is for — its a free download! But I digress. So let’s move on to an example.

Suppose that you have a <List> component, and that you’d like it to add top and bottom CSS classes to the first and last children. Let’s also say that when your list only has one child, it should add both classes to the child.

<List><divclassName="top bottom"/></List>

In the case of a single child, this can be accomplished by cloning it and adding a className prop with the value top bottom, as so.

But while this works when <List> only receives a single child, it fails spectacularly when you you pass multiple children. You can check this for yourself — just add a few more band names to the the list in the above example.

Components never know what type of data their children prop will be. It could be an array, it could be an element, it could be text. It could be anything.

Working with data of type “anything” is never fun. Luckily, React provides some tools to help on its React.Children object. In particular, the React.Children.toArray() function will come in handy for this example.

// Always returns an array.
React.Children.toArray(children)

The React.Children.toArray() function does exactly what you’d imagine it does. You pass it a children prop, and it’ll return an array. If children only contains one child, it’ll wrap that child in an array, which you can then slice() and concat() like a ninja.

To put this all together, here’s how you’d use React.Children.toArray() with React.cloneElement() to add top and bottom classes to the List component’s children.

One of the problems with cloning children is that it can be pretty easy to get wrong. There are a lot of edge cases, and unlike with render functions, you’ll need to take account of all of those edge cases within the component that does the cloning.

For example, if you were to add a className prop to the top or bottom <div> in the above demo, the <List> component would remove it in place of the top or bottom class. You can see this in the following example, where I’ve added the highlight class to the top two rows, but only the middle row is actually highlighted.

To fix this, you’ll need to append a string to the className instead of replacing it. In fact, let’s do this as an exercise!

Your task is to ensure that the top and bottom elements of the list render with both their original class, as well as a top or bottom class.

Hint: as React.cloneElement() only knows how to replace props, you’ll need to figure out how to build the new string by inspecting the original element.