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?
Advertisement
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
.
See the code comment why I don’t recommend this approach.
const Child = ({ childName, sayHello }) => ( <button onClick={() => sayHello(childName)}>{childName}</button> ); function Parent({ children }) { // We pass this `sayHello` function into the child elements. function sayHello(childName) { console.log(`Hello from ${childName} the child`); } 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, { sayHello }); } return child; }); return <div>{childrenWithProps}</div> } function App() { // This approach is less type-safe and Typescript friendly since it // looks like you're trying to render `Child` without `sayHello`. // It's also confusing to readers of this code. return ( <Parent> <Child childName="Billy" /> <Child childName="Bob" /> </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 via 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 actual children:
const Child = ({ childName, sayHello }) => ( <button onClick={() => sayHello(childName)}>{childName}</button> ); function Parent({ children }) { function sayHello(childName) { console.log(`Hello from ${childName} the child`); } // `children` of this component must be a function // which returns the actual children. We can pass // it args to then pass into them as props (in this // case we pass `sayHello`). return <div>{children(sayHello)}</div> } function App() { // sayHello is the arg we passed in Parent, which // we now pass through to Child. return ( <Parent> {(sayHello) => ( <React.Fragment> <Child childName="Billy" sayHello={sayHello} /> <Child childName="Bob" sayHello={sayHello} /> </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>