How to pass props to {this.props.children}

I'm trying to find the proper way to define some components which could be used in a generic way:

<Parent>
  <Child value="1">
  <Child value="2">
</Parent>

There is a logic going on for rendering between parent and children components of course, you can imagine <select> and <option> as an example of this logic.

This is a dummy implementation for the purpose of the question:

var Parent = React.createClass({
  doSomething: function(value) {
  },
  render: function() {
    return (<div>{this.props.children}</div>);
  }
});

var Child = React.createClass({
  onClick: function() {
    this.props.doSomething(this.props.value); // doSomething is undefined
  },
  render: function() {
    return (<div onClick={this.onClick}></div>);
  }
});

The question is whenever you use {this.props.children} to define a wrapper component, how do you pass down some property to all its children?


Solution 1:

Cloning children with new props

You can use React.Children to iterate over the children, and then clone each element with new props (shallow merged) using React.cloneElement. For example:

const Child = ({ doSomething, value }) => (
  <button onClick={() => doSomething(value)}>Click Me</button>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log("doSomething called by child with value:", value);
  }

  const childrenWithProps = React.Children.map(children, child => {
    // Checking isValidElement is the safe way and avoids a typescript
    // error too.
    if (React.isValidElement(child)) {
      return React.cloneElement(child, { doSomething });
    }
    return child;
  });

  return <div>{childrenWithProps}</div>
}

function App() {
  return (
    <Parent>
      <Child value={1} />
      <Child value={2} />
    </Parent>
  );
}

ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<div id="container"></div>

Calling children as a function

Alternatively, you can pass props to children with render props. In this approach, the children (which can be children or any other prop name) is a function which can accept any arguments you want to pass and returns the children:

const Child = ({ doSomething, value }) => (
  <button onClick={() => doSomething(value)}>Click Me</button>
);

function Parent({ children }) {
  function doSomething(value) {
    console.log("doSomething called by child with value:", value);
  }

  // Note that children is called as a function and we can pass args to it.
  return <div>{children(doSomething)}</div>
}

function App() {
  // doSomething is the arg we passed in Parent, which
  // we now pass through to Child.
  return (
    <Parent>
      {doSomething => (
        <React.Fragment>
          <Child doSomething={doSomething} value={1} />
          <Child doSomething={doSomething} value={2} />
        </React.Fragment>
      )}
    </Parent>
  );
}

ReactDOM.render(<App />, document.getElementById("container"));
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
<div id="container"></div>

Instead of <React.Fragment> or simply <> you can also return an array if you prefer.

Solution 2:

For a slightly cleaner way to do it, try:

<div>
    {React.cloneElement(this.props.children, { loggedIn: this.state.loggedIn })}
</div>

Edit: To use with multiple individual children (the child must itself be a component) you can do. Tested in 16.8.6

<div>
    {React.cloneElement(this.props.children[0], { loggedIn: true, testPropB: true })}
    {React.cloneElement(this.props.children[1], { loggedIn: true, testPropA: false })}
</div>

Solution 3:

Try this

<div>{React.cloneElement(this.props.children, {...this.props})}</div>

It worked for me using react-15.1.

Use {...this.props} is suggested in https://reactjs.org/docs/jsx-in-depth.html#spread-attributes