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?
Passing Props to Children in React Using the Context APIContext allows you to pass props down to an entire tree of components. It is extremely useful because you have an App component at the top but want to pass down an event handler to a child component at the bottom of the tree.
There is no way to pass props up to a parent component from a child component. We will revisit this caveat later in this tutorial. It's also important to note that React's props are read only (immutable). As a developer, you should never mutate props but only read them in your components.
Children are props If you want to pass things to this button, you would use a prop. If you want to make our button say more than just "I am a button," you can pass children to it. By passing children in this way, you are passing it to the component by position.
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>
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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With