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

javascriptreact-jsxreactjs

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?

Best Answer

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.