New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to pass props to components when you use <Route components={{}} /> attribute ? #4105

Closed
wzup opened this Issue Oct 27, 2016 · 29 comments

Comments

@wzup
Copy link

wzup commented Oct 27, 2016

So react-router's component has components attribute.

<Route path="latest" components={{sidebar: Sidebar, content: ContentLayout}} />

Then in an appropriate component I can reference components through props:

{this.props.sidebar}
{this.props.content}

But without react-router I would do this, I can pass my component custom props:

<Sidebar names={ ['foo', 'bar'] } isOpen={true} num={10} />
<ContentLayout type={ contentType } background={'#fff'} title={titleOne} />

My questions is. How can I pass props to my component when I use components attribute of React Router?

@wzup wzup changed the title How to pass params to components when you use <Route components={{}} /> attribute ? How to pass props to components when you use <Route components={{}} /> attribute ? Oct 27, 2016

@timdorr

This comment has been minimized.

Copy link
Collaborator

timdorr commented Oct 27, 2016

There is an example for this: https://github.com/ReactTraining/react-router/blob/master/examples/passing-props-to-children/app.js

The gist is to use React.cloneElement.

@timdorr timdorr closed this Oct 27, 2016

@thenikso

This comment has been minimized.

Copy link

thenikso commented Oct 28, 2016

hi @wzup I just got this very problem and I noticed that in the child component i have this.props.route which, in your example you could do (I think):

<Route path="latest" components={{sidebar: Sidebar, content: ContentLayout}} something="foo" />

And in, let's say Sidebar you should be able to do:

this.props.route.something

Hope this helps!

@tcosentino

This comment has been minimized.

Copy link

tcosentino commented Mar 15, 2017

@themirror178

This comment has been minimized.

Copy link

themirror178 commented Mar 16, 2017

@thenikso what version of React-Router? This does not work in react-router-dom 4.0

@ddillert

This comment has been minimized.

Copy link

ddillert commented Mar 17, 2017

@themirror178

In react-router v4, we usually do the following to pass in a ProductPage component:

<Route path='/' component={ProductPage} />

When you want to use props, you can prepare a function that returns your component with the desired props. Here I'm preparing to pass in a toggleSidebarOn prop:

    const MyProductPage = (props) => {
      return (
        <ProductPage 
          toggleSidebarOn={this.toggleSidebarOn.bind(this)}
          {...props}
        />
      );
    }

Then you use the render prop of the <Route /> instead of its component prop. Do not use both and do not use the component prop as this leads to undesired remounting and might break your app (e.g. for me CSS transitions for the sidebar animation stopped working properly).

Using the render prop for the MyProductPage we can then pass in our toggleSidebarOn prop to its target route:

<Router>
  <div>
    <Switch>
      <Route exact path="/products" render={MyProductPage} />
      <Route exact path="/perspectives" component={PerspectivePage}/>
      <Route component={NotFound}/>
    </Switch>
  </div>
</Router>

Hope this helps!

@tchaffee

This comment has been minimized.

Copy link

tchaffee commented Mar 25, 2017

In react-router v4, you can do this in a reusable way so you don't have to hand write boilerplate for each component that needs properties. A couple of functions will do the trick (or head all the way to the bottom of this comment for a way to pass properties with no supporting code). renderMergedProps can be reused in any custom component that will return a <Route> but here we just keep it simple and create a PropsRoute that will pass all the properties to the component that was specified on the PropsRoute.

const renderMergedProps = (component, ...rest) => {
  const finalProps = Object.assign({}, ...rest);
  return (
    React.createElement(component, finalProps)
  );
}

const PropsRoute = ({ component, ...rest }) => {
  return (
    <Route {...rest} render={routeProps => {
      return renderMergedProps(component, routeProps, rest);
    }}/>
  );
}

Use the PropsRoute to create any route you want using any component and the component will get the properties. Below, we have four different components all being passed different properties. For example the Books component below will get the booksGetter property, which is different for the two routes having to do with books.

<Router>
    <Switch>
      <PropsRoute path='/login' component={Login} auth={auth} authenticatedRedirect="/" />
      <PropsRoute path='/allbooks' component={Books} booksGetter={getAllBooks} />
      <PropsRoute path='/mybooks' component={Books} booksGetter={getMyBooks} />
      <PropsRoute path='/trades' component={Trades} user={user} />
    </Switch>
</Router>

For something with a little more logic here is how you could use renderMergedProps to create a <PrivateRoute> that either passes properties to the component, or redirects if the user is not logged in.

const PrivateRoute = ({ component, redirectTo, ...rest }) => {
  return (
    <Route {...rest} render={routeProps => {
      return auth.loggedIn() ? (
        renderMergedProps(component, routeProps, rest)
      ) : (
        <Redirect to={{
          pathname: redirectTo,
          state: { from: routeProps.location }
        }}/>
      );
    }}/>
  );
};

And here is how you could use it. Note that <PropsRoute> is not protected, but <PrivateRoute> is.

<Router>
  <Switch>
    <Route exact path="/" component={Home} />
    <PropsRoute path='/allbooks' component={Books} booksGetter={getAllBooks} />
    <PrivateRoute path='/mybooks' component={Books} redirectTo="/" booksGetter={getMyBooks} />
    <PrivateRoute path='/trades' component={Trades} redirectTo="/" user={user} />
  </Switch>
</Router>

Finally, there is a shorthand way you can pass properties now that it's clear what <PropsRoute> is doing and how to implement components with more logic. For me the <PropsRoute> is a little easier on the eye, but some folks might like the style below which requires no other supporting code.

<Route path='/mybooks' render={routeProps => <Books {...routeProps} booksGetter={getMyBooks}/>} />
@kimasendorf

This comment has been minimized.

Copy link

kimasendorf commented Mar 27, 2017

This works for me, but only if I remove the ...rest prop. As soon as I add ...rest as incoming property for PrivateRoute my webpack bundling fails. Is there any documentation about the ...rest?

@tchaffee

This comment has been minimized.

Copy link

tchaffee commented Mar 27, 2017

@kimasendorf the ...rest is ES6 spread syntax. You may need to add a babel compiler to webpack. I'm a bit of a beginner at webpack at the moment since I'm using create-react-app which takes care of that for me.

@kimasendorf

This comment has been minimized.

Copy link

kimasendorf commented Mar 27, 2017

I have a babel compiler running and I am using a few components with ...props. I.e.:

const LogInAndSignUp = (props) => (
  <div>
    <FlatButton
      {...props}
      label="Log in"
      containerElement={<Link to="/login"/>}
    />
    <FlatButton
      {...props}
      label="Sign up"
      containerElement={<Link to="/signup"/>}
    />
  </div>
);

Is ...rest basically the same?

@kimasendorf

This comment has been minimized.

Copy link

kimasendorf commented Mar 27, 2017

Looks like the spread operator is not part of the babel-preset-es2015 and I need to use babel-preset-stage-2 too. Using the ES7 Spread Operator with Webpack.

@yonatanmn

This comment has been minimized.

Copy link

yonatanmn commented Apr 5, 2017

easiest solution for me:

<Route path="/abc" render={()=><TestWidget num="2" someProp={100}/>}/>
@seeliang

This comment has been minimized.

Copy link

seeliang commented Apr 12, 2017

@yonatanmn Yes, this is my initial approach, but I could not get the params :(

@Macmee

This comment has been minimized.

Copy link

Macmee commented Apr 15, 2017

@seeliang

how about:

<Route exact path="/abc" render={props => <TestWidget someProp="2" {...props} />} />

and what I do is I actually spread {...props.match.params} because then on TestWidget you can access the URL parameters with just this.props.urlVariableHere.

(note to googlers; the above works on v4)

@omarjmh

This comment has been minimized.

Copy link

omarjmh commented Apr 22, 2017

this works for me:
<Route exact path={"/"} component={() => <Start socket={socket} addUser={addUser}/>}/>

@seeliang

This comment has been minimized.

Copy link

seeliang commented Apr 22, 2017

Wow, thank you both @omarjmh and @Macmee,
i will give a go tomorrow. :)

@tchaffee

This comment has been minimized.

Copy link

tchaffee commented Apr 25, 2017

@omarjmh per the docs, that might not be an ideal approach. "When you use component (instead of render, below) the router uses React.createElement to create a new React element from the given component. That means if you provide an inline function, you are creating a new component every render. This results in the existing component unmounting and the new component mounting instead of just updating the existing component. For inline rendering, use the render prop"

@seeliang

This comment has been minimized.

Copy link

seeliang commented Apr 27, 2017

@tchaffee Thanks for the update. so @Macmee 's feedback could be better solution in this case?

@tchaffee

This comment has been minimized.

Copy link

tchaffee commented Apr 28, 2017

@seeliang since I wrote the same same thing as @Macmee a little further back in the comments, I'd have to say yes ;-)

@zulucoda

This comment has been minimized.

Copy link

zulucoda commented May 1, 2017

Hi All just to add more clarity on what best worked for me after trying various ways as advised by @tchaffee, @yonatanmn and @Macmee

Also just to add more context: I'm using Redux to manage my state and my AppContainer is mapping state to props and connecting my App. From my App props are passed down to other components.

So when I tried this solution (@yonatanmn and @Macmee):
<Route path="/abc" render={props => <TestWidget someProp="2" {...props} />} />
I only got react-router props example:
Object {match: Object, location: Object, history: Object, staticContext: undefined, path: "/somePath"…}

When I tried @tchaffee solution:
const state = { ...props };
<PropsRoute path="/somePath" component={SomeComponent} state={state} />

With this solution I was able to separate react-router props from redux props.
Object {match: Object, location: Object, history: Object, staticContext: undefined, path: "/somePath", state: Object}
This worked better for me, I thought I would share that.

@tchaffee, @yonatanmn and @Macmee thanks again for posting various ways to do this 👍

@elverskog

This comment has been minimized.

Copy link

elverskog commented May 28, 2017

I was having trouble trying to get newly updated props (say based on window size changes) getting passed with @tchaffee 's solution. It would only render once. So I combined the basic idea with the one from @yonatanmn. Thanks guys.

<Route path="/" render={routeProps => <Home {...Object.assign({}, routeProps, customProps)} />} />

This version is passing location, from Router, and re-renders whenever anything in customProps changes.

mamodom added a commit to lycan-city/werewolf-moderator that referenced this issue May 29, 2017

Managed to avoid mapping actionCreators to props in every single cont…
…ainer component

As far as I understood in the current version of react router this is
the only way to pass props to the component a route renders, see ReactTraining/react-router#4105 (comment)
@stevematdavies

This comment has been minimized.

Copy link

stevematdavies commented Jun 21, 2017

The issue with the render={()=><Component props={..

Is that the component is only rendered once inside the router. So if you go to that route, and then go back with the history, the component is not rendered again, which is no good if you want to update things.

@EmptyCrown

This comment has been minimized.

Copy link

EmptyCrown commented Jul 1, 2017

I'm using a format where I have something of the form:

<Router>
  <App>
    <Route path='/' component={Comp1} />
    <Route path='/login' component={Comp2} />
    <Route path='/account' component={Comp3} />
  </App>
</Route>

In my App component, I'm generating a bunch of info that I'd like to pass as props to the child components Comp1, Comp2, Comp3. This was simple in React Router v3, but now it seems that with v4, the Route components are in the way. How do I pass these props from App into the individual components?

@ReactTraining ReactTraining deleted a comment from k7sleeper Jul 5, 2017

@FeloVilches

This comment has been minimized.

Copy link

FeloVilches commented Jul 8, 2017

@thenikso

Your code is almost the same as:

<something foo={bar} keyword="stuff">

"Yesterday I went to a place, and I did a thing, and after a time, I did another thing, and then I did a thing with a person."

@bora89

This comment has been minimized.

Copy link

bora89 commented Jul 13, 2017

I can not believe that an essential feature requires that much headache with coding it. Why it is not described in the documentation?

@bitcloud

This comment has been minimized.

Copy link

bitcloud commented Jul 13, 2017

@ahmetcetin

This comment has been minimized.

Copy link

ahmetcetin commented Jul 17, 2017

just my 2c:
You can use withRouter function of react-router, it plays well with redux also:

In your Component.jsx:

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
...
class Component extends Component {}
// or const Component = (props) => ();
...
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Component));

@tchaffee

This comment has been minimized.

Copy link

tchaffee commented Jul 17, 2017

@bora89 The docs are very terse. More real life examples would definitely help. In order to learn the new version I had to read the docs several times, go to stackoverflow for examples, do experiments, and attempt to answer questions myself on stackoverflow. Once you get it, it all makes sense and it feels like an elegant solution that is very flexible and only does what it needs to do. More examples would help that happen much quicker.

@ajsharp

This comment has been minimized.

Copy link

ajsharp commented Jul 25, 2017

This seems like something the library should support out of the box, as it's a fairly common use case.

@timdorr

This comment has been minimized.

Copy link
Collaborator

timdorr commented Jul 25, 2017

Locking this one out. Sorry, but there are tons of various examples above and in the documentation and this conversation is just repeating the same points over and over. This seems to be the preferred winner: #4105 (comment)

@ReactTraining ReactTraining locked and limited conversation to collaborators Jul 25, 2017

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.