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

Tags: , ,



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?

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>
);

class Parent extends React.Component{
    doSomething = value => {
        console.log("doSomething called by child with value:", value);
    }

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

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

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

ReactDOM.render(<App />, document.getElementById("container"));

Fiddle: https://jsfiddle.net/2q294y43/2/

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>
);

class Parent extends React.Component{
    doSomething = value => {
        console.log("doSomething called by child with value:", value);
    }

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

function App(){
    return (
        <Parent>
            {doSomething => (
                <React.Fragment>
                    <Child doSomething={doSomething} value={1} />
                    <Child doSomething={doSomething} value={2} />
                </React.Fragment>
            )}
        </Parent>
    );
}
 
ReactDOM.render(<App />, document.getElementById("container"));

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

Fiddle: https://jsfiddle.net/ferahl/y5pcua68/7/



Source: stackoverflow