Home » Reactjs » How to pass props to {this.props.children}

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

Posted by: admin November 29, 2017 Leave a comment

Questions:

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?

Answers:

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

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

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

  render() {
    const { children } = this.props;

    var childrenWithProps = React.Children.map(children, child =>
      React.cloneElement(child, { doSomething: this.doSomething }));

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

ReactDOM.render(
  <Parent>
    <Child value="1" />
    <Child value="2" />
  </Parent>,
  document.getElementById('container')
);

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

Questions:
Answers:

For a slightly cleaner way to do it, try:

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

Note: this will only work if there is a single child, and it is a valid React element.

Questions:
Answers:

Try this

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

It worked for me using react-15.1.

Questions:
Answers:

Scenario 1 – pass props to direct children.

See all other answers

Scenario 2 – pass data / dependencies through the component tree via context

Keep reading.

React’s context makes data available / enables dependency injection to all children and subchildren components, here is an example

class Child extends React.Component {
  render() {
    return (
      <div onClick={() => this.context.doSomething(this.props.value)}>
        Click Me
      </div>
    );
  }
};


// Access parent context by defining contextTypes
Child.contextTypes = {
  doSomething: React.PropTypes.func
};

class Parent extends React.Component {
  // passes the information down to its children
  getChildContext() {
    return { doSomething : this.doSomething };
  }

  doSomething(value) {
    console.log('doSomething called by child with value:', value);
  },

  render() {
    return <div>{this.props.children}</div>
  }
};

// make information available to its children
Parent.childContextTypes = {
  doSomething: React.PropTypes.func
};

ReactDOM.render(
  <Parent>
    <Child value="1" />
    <Child value="2" />
  </Parent>,
  document.getElementById('container')
);

This article.

Disclaimer: React’s documentation mentions that context is an experimental API, but it hasn’t changed for years now. Also, it really is needed in some cases, this is why (one of) the most famous React libraries makes use of it internally – React Router. If you want to go on the super safe side though, you might want to abstract context in some way that fits your needs.

Questions:
Answers:

You can use React.cloneElement, it’s better to know how it works before you start using it in your application. It’s introduced in React v0.13, read on for more information, so something along with this work for you:

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

So bring the lines from React documentation for you to understand how it’s all working and how you can make use of them:

In React v0.13 RC2 we will introduce a new API, similar to
React.addons.cloneWithProps, with this signature:

React.cloneElement(element, props, ...children);

Unlike cloneWithProps, this new function does not have any magic
built-in behavior for merging style and className for the same reason
we don’t have that feature from transferPropsTo. Nobody is sure what
exactly the complete list of magic things are, which makes it
difficult to reason about the code and difficult to reuse when style
has a different signature (e.g. in the upcoming React Native).

React.cloneElement is almost equivalent to:

<element.type {...element.props} {...props}>{children}</element.type>

However, unlike JSX and cloneWithProps, it also preserves refs. This
means that if you get a child with a ref on it, you won’t accidentally
steal it from your ancestor. You will get the same ref attached to
your new element.

One common pattern is to map over your children and add a new prop.
There were many issues reported about cloneWithProps losing the ref,
making it harder to reason about your code. Now following the same
pattern with cloneElement will work as expected. For example:

var newChildren = React.Children.map(this.props.children, function(child) {
  return React.cloneElement(child, { foo: true })
});

Note: React.cloneElement(child, { ref: ‘newRef’ }) DOES override the
ref so it is still not possible for two parents to have a ref to the
same child, unless you use callback-refs.

This was a critical feature to get into React 0.13 since props are now
immutable. The upgrade path is often to clone the element, but by
doing so you might lose the ref. Therefore, we needed a nicer upgrade
path here. As we were upgrading callsites at Facebook we realized that
we needed this method. We got the same feedback from the community.
Therefore we decided to make another RC before the final release to
make sure we get this in.

We plan to eventually deprecate React.addons.cloneWithProps. We’re not
doing it yet, but this is a good opportunity to start thinking about
your own uses and consider using React.cloneElement instead. We’ll be
sure to ship a release with deprecation notices before we actually
remove it so no immediate action is necessary.

more here

Questions:
Answers:

I needed to fix accepted answer above to make it work using that instead of this pointer. This within the scope of map function didn’t have doSomething function defined.

var Parent = React.createClass({
doSomething: function() {
    console.log('doSomething!');
},

render: function() {
    var that = this;
    var childrenWithProps = React.Children.map(this.props.children, function(child) {
        return React.cloneElement(child, { doSomething: that.doSomething });
    });

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

Update: this fix is for ECMAScript 5, in ES6 there is no need in var that=this

Questions:
Answers:

Cleaner way considering one or more children

<div>
   { React.Children.map(this.props.children, child => React.cloneElement(child, {...this.props}))}
</div>

Questions:
Answers:

React-router versions 0.x – 3.x were a mess. Version 4 is the complete rewrite the proper way.

You no longer need {this.props.children}. Now you can wrap your child component using component or render in Match and pass your props as usual:

<BrowserRouter>
  <div>
    <ul>
      <li><Link to="/">Home</Link></li>
      <li><Link to="/posts">Posts</Link></li>
      <li><Link to="/about">About</Link></li>
    </ul>

    <hr/>

    <Route path="/" exact component={Home} />
    <Route path="/posts" render={() => (
      <Posts
        value1={1}
        value2={2}
        data={this.state.data}
      />
    )} />
    <Route path="/about" component={About} />
  </div>
</BrowserRouter>

Questions:
Answers:

None of the answers address the issue of having children that are NOT React components, such as text strings. A workaround could be something like this:

// Render method of Parent component
render(){
    let props = {
        setAlert : () => {alert("It works")}
    };
    let childrenWithProps = React.Children.map( this.props.children, function(child) {
        if (React.isValidElement(child)){
            return React.cloneElement(child, props);
        }
          return child;
      });
    return <div>{childrenWithProps}</div>

}

Questions:
Answers:

Further to @and_rest answer, this is how I clone the children and add a class.

<div className="parent">
    {React.Children.map(this.props.children, child => React.cloneElement(child, {className:'child'}))}
</div>

Questions:
Answers:

Is this what you required?

var Parent = React.createClass({
  doSomething: function(value) {
  }
  render: function() {
    return  <div>
              <Child doSome={this.doSomething} />
            </div>
  }
})

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

Questions:
Answers:

The slickest way to do this:

    {React.cloneElement(this.props.children, this.props)}

Questions:
Answers:

Parent.jsx:

import React from 'react';

const doSomething = value => {};

const Parent = props => (
  <div>
    {
      !props || !props.children 
        ? <div>Loading... (required at least one child)</div>
        : !props.children.length 
            ? <props.children.type {...props.children.props} doSomething={doSomething} {...props}>{props.children}</props.children.type>
            : props.children.map((child, key) => 
              React.cloneElement(child, {...props, key, doSomething}))
    }
  </div>
);

Child.jsx:

import React from 'react';

/* but better import doSomething right here,
   or use some flux store (for example redux library) */
export default ({ doSomething, value }) => (
  <div onClick={() => doSomething(value)}/>
);

and main.jsx:

import React from 'react';
import { render } from 'react-dom';
import Parent from './Parent';
import Child from './Child';

render(
  <Parent>
    <Child/>
    <Child value='1'/>
    <Child value='2'/>
  </Parent>,
  document.getElementById('...')
);

see example here: https://plnkr.co/edit/jJHQECrKRrtKlKYRpIWl?p=preview