30

I'm using React-router for the first time and I don't know how to think in it yet. Here's how i'm loading my components in nested routes.

entry point .js

ReactDOM.render(
    <Router history={hashHistory} >
        <Route path="/" component={App}>
            <Route path="models" component={Content}>
        </Route>
    </Router>, 
    document.getElementById('app')
);

App.js

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

So the child of my App is the Content component I sent in. I'm using Flux and my App.js has the state and listens for changes, but I don't know how to pass that state down to this.props.children. Before using react-router my App.js defines all children explicitly, so passing state was natural but I don't see how to do it now.

Patrick
  • 827
  • 3
  • 14
  • 20
  • 1
    Possible duplicate of [How to pass props to {this.props.children}](http://stackoverflow.com/questions/32370994/how-to-pass-props-to-this-props-children) – Rick Runyon Mar 07 '16 at 03:43

3 Answers3

33

This question boils down to, how do you pass props to children?

June 2018 answer

Today's tech:


Assuming some stateful component:

import React from 'react'
import { BrowserRouter, Route } from 'react-router-dom'

// some component you made
import Title from './Title'

class App extends React.Component {
  // this.state
  state = { title: 'foo' }

  // this.render
  render() {
    return (
      <BrowserRouter>

        // when the url is `/test` run this Route's render function:
        <Route path="/:foobar" render={

          // argument is props passed from `<Route /`>
          routeProps => 

            // render Title component
            <Title 
              // pass this.state values
              title={this.state.title}

              // pass routeProps values (url stuff)
              page={routeProps.match.params.foobar} // "test"
            />

        } />

      </BrowserRouter>
    )
  }
}

This works because this.props.children is a function:

// "smart" component aka "container"
class App extends React.Component {
  state = { foo: 'bar' }
  render() {
    return this.props.children(this.state.foo)
  }
}

// "dumb" component aka "presentational"
const Title = () => (
  <App>
    {title => <h1>{title}</h1>}
  </App>
)

Example on codesandbox

My previous oldschool answer that I wouldn't recommend anymore:

Using a couple of React helper methods you can add state, props and whatever else to this.props.children

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

  return <div>{children}</div>
}

Then your child component can access this via props, this.props.foo.

azium
  • 20,056
  • 7
  • 57
  • 79
  • Yea, I was reading about the children helper and saw this. Why use the Map rather than the ForEach though? – Patrick Mar 07 '16 at 03:58
  • 1
    It's generally advised not to mutate data if you can help it. This is good practice in programming generally, not just for React. Only make mutations if there is no other possible solution---this is a rare case. – azium Mar 07 '16 at 04:03
  • I agree about mutations but I guess I don't see the connection - does ForEach necessarily mutate state? – Patrick Mar 07 '16 at 04:05
  • Well `cloneElement` returns a new child object so you need to return a new `children` array/object. `Children.forEach` doesn't return anything, neither does `Array.prototype.forEach`. If you don't return anything, you affect no change unless you mutate something or cause a side effect (save to database / make ajax call etc) – azium Mar 07 '16 at 04:12
  • Thank you for this answer, works like a charm. However, I'm getting a propType validation warning from the child component, saying the props are undefined. I can use them without problems, but I don't know why these warnings are thrown? Do you have any idea? –  Feb 27 '17 at 11:21
  • @OleBläsing I would make another SO question and link it here if you want. – azium Feb 27 '17 at 15:26
11

You can use the React method "cloneElement" to accomplish this. When you clone the element, you can pass in props at that time. Use the clone instead of the original in your render fn. eg:

    render: function() {
    var childrenWithProps = React.cloneElement(this.props.children, {someProp: this.state.someProp});
    return (
      <div>
        <Header />
        {childrenWithProps}
      </div>
    );
  }
  • Missing curly braces around `childrenWithProps` – azium Mar 07 '16 at 03:46
  • Would your example clone all children or just one in the case when this.props.children only has one element? I saw the other example on stackoverflow but it seemed wrong that I would need to manually iterate through them just to do basic props passing. – Patrick Mar 07 '16 at 03:51
  • 1
    @Patrick If you look at my answer you'll notice `Children.map` first. This is needed, typically because `children` can be an object or an array, and in the latter case, you clone each element. – azium Mar 07 '16 at 03:53
  • 1
    @Patrick as @azium said, my example will only work if `this.props.children` is a single object, not an array. And, `Children.map` will work, even if `this.props.children` is a single object. So their suggestion is a better option than mine. – Jess Scheuring Mar 08 '16 at 04:54
  • Thanks regardless! I'm glad to have help – Patrick Mar 08 '16 at 04:59
2

There's also the option of using Context. React-Router relies on it to give access to the Router object in the route components.

From another answer I gave on a similar question:

I quickly put together an example using contexts on codepen. MainLayout defines some properties that could be used by the children using the context: users and widgets. These properties are used by the UserList and WidgetList components. Notice they need to define what they need to access from the context in the contextTypes object.

var { Router, Route, IndexRoute, Link } = ReactRouter

var MainLayout = React.createClass({
  childContextTypes: {
    users: React.PropTypes.array,
    widgets: React.PropTypes.array,
  },
  getChildContext: function() {
    return {
      users: ["Dan", "Ryan", "Michael"], 
      widgets: ["Widget 1", "Widget 2", "Widget 3"]
    };
  },
  render: function() {
    return (
      <div className="app">
        <header className="primary-header"></header>
        <aside className="primary-aside">
          <ul>
            <li><Link to="/">Home</Link></li>
            <li><Link to="/users">Users</Link></li>
            <li><Link to="/widgets">Widgets</Link></li>
          </ul>
        </aside>
        <main>
          {this.props.children}
        </main>
      </div>
      )
  }
})

var Home = React.createClass({
  render: function() {
    return (<h1>Home Page</h1>)
  }
})

var SearchLayout = React.createClass({
  render: function() {
    return (
      <div className="search">
        <header className="search-header"></header>
        <div className="results">
          {this.props.children}
        </div>
        <div className="search-footer pagination"></div>
      </div>
      )
  }
})

var UserList = React.createClass({
  contextTypes: {
    users: React.PropTypes.array
  },
  render: function() {
    return (
      <ul className="user-list">
        {this.context.users.map(function(user, index) {
          return <li key={index}>{user}</li>;  
        })}
      </ul>
      )
  }
})

var WidgetList = React.createClass({
  contextTypes: {
    widgets: React.PropTypes.array
  },
  render: function() {
    return (
      <ul className="widget-list">
        {this.context.widgets.map(function(widget, index) {
          return <li key={index}>{widget}</li>;  
        })}
      </ul>
      )
  }
})

var Routes = React.createClass({
  render: function() {
    return <Router>
        <Route path="/" component={MainLayout}>
          <IndexRoute component={Home} />
          <Route component={SearchLayout}>
            <Route path="users" component={UserList} />
            <Route path="widgets" component={WidgetList} />
          </Route> 
        </Route>
      </Router>;
  }
})

ReactDOM.render(<Routes/>, document.getElementById('root'))
Community
  • 1
  • 1
Alex Chirițescu
  • 688
  • 4
  • 13