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
Passing Props to Child Components with explicit props defined per route #1857
Comments
@joewood Passing state down from parent to child components seems to go against the principles of React IMHO. For passing props there's an example in master using But it might be worth looking into Contexts too. That article is a few months old but explains the concept well. |
@jdelight not sure I understand how passing state from parent to child is an anti-pattern. I may be misunderstanding here, but passing state down to contained components as properties is kind of the mainstay of React. A parent knows the contract of the components it consumes. What I'm trying to do is to avoid creating a dependency between the child components and the parent app. The child components shouldn't have to know the shape of the props of where they're being used. If the parent component is a simple data container, then that means all the components participating in the route map are exposed to the entire state of the container. |
Not much discussion in #1531 :-) |
You should think of your route components as entry points into the app that don't know or care about the parent, and the parent shouldn't know/care about the children. We have some nice hooks in 1.0 to build data loading abstractions that we hope to highlight with a sample implementation in |
OK, I'll have to look into the 1.0beta code a bit. As it stands Route components have to know about the parent's properties because that's what they receive as their properties. I guess that's fine as you treat them as part of the app and not supposed to be reusable. It just seems cleaner and more modular to me to specify the properties in the route map in the form of a reducer expression. |
Have a look at the new |
OK, this page helps: Router.md. The problem with this is that only the properties are passed through and not the state. If, as suggested, these routes are entry points to the app the composability of the GUI really suffers. It feels like the design is geared towards using Redux, where a single store is accessed to pull down the properties rather than propagating state to properties in more typical React. Am I missing something? |
@joewood I am not sure if you ever figured this out, but what I am doing to solve this is the following I pass
into the initial router. I did the same React.cloneElement inside the AboutUsComponent but used props instead of state, since I passed the siteData into AboutUsComponent as a prop.
I hope this makes sense, and I hope it may help someone else. I read a lot of confused comments regarding this, with no real "example" explanations. |
Hi epitaphmike, is it possible to set up a small working example? I was also searching for the answer and looking for an live experience. It would be very helpful... Kind Regards, |
@OliverOliverOliverAnders isn't his example a working one? The essential part is if you want to pass data from parent to child, initially you have: var AboutUsComponent = React.createClass({
render() {
return (
<div className="about-us-page">
{this.props.children}
</div>
);
}
}); But instead, you clone the children with the passed data: {React.cloneElement(this.props.children, {dataChildrenNeeded: this.props.parentsData})} You can read more details from: #1531 (comment) |
Can't comment on #1531 since that one was closed, but I have to agree with the OP there that {this.props.renderChildren(moreProps)} Though you might want to be able to account for the specific component being rendered with some sort of callback: {this.props.renderChildren(function(child) { return propsDependingOnChild(child); })} |
It's not an anti-pattern. Nothing is getting "instantiated". Your component class is not getting
|
Whether or not it calls |
Of what object? What do you think a React element is, exactly? |
I'm not sure I understand the question, or how it's relevant. You seem to be taking a pretty condescending attitude, though, and not addressing my point that it makes it impossible to validate with |
@mockdeep In DEV, React's cloneElement does check propTypes. |
But the problem is that it also checks |
That's a valid point. But stepping back, I normally treat my route components as top-level, container-style components. They don't receive props outside of the ones router provides. They dish those out to presentational components that can check PropTypes like normal. This keeps me from building monolithic über-components that pack in so much functionality that I've basically built everything in one place and I'm not getting any of the benefits of the toolchain I've chosen. I'd try to keep route components aware of only router-provided props and try to maintain SRP. |
That's definitely correct. The actual anti-pattern is passing props through route boundaries – in general you just shouldn't be doing this. |
I am on the same page as @timdorr. My route components are top-level, container-style components as well. I have actually altered the example I used above, since I am now using express too. I am leaning on another repo https://github.com/paypal/react-engine which is a composite render engine for universal (isomorphic) express apps to render both plain react views and react-router views. My routes file handles nothing but the routes. The props are all handled in the App and AboutUsWrapper files and passed down accordingly. |
👍 |
Thanks for the feedback. I think that makes sense, though it seems kind of out of scope for the router to push architectural decisions like that. In my case, I'm trying to have my var AppBase = React.createClass({
...
render: function () {
<Boilerplate globalState={this.state} />
{React.cloneElement(this.props.children, {globalState: this.state})}
}
});
ReactDOM.render(
<Router history={history}>
<Route path='/' component={AppBase}>
<Route 'whatwhat' ... />
</Route>
</Router>,
Document.getElementById('app-base')
); How do you go about keeping state across containers without having some global state to pass down? |
Redux! 😄 |
Usually if you're passing props across route boundaries your parent route knows exactly what it's rendering: <Route path="/inbox" component={Inbox}>
<Route path=":messageId" component={Message}/>
<IndexRoute component={InboxStats}/>
</Route>
const Inbox = React.createClass({
render() {
return (
<div>
{/* this is only ever `Message`, except the weird case
of `InboxStats` which doesn't need the prop */}
{React.cloneElement(this.props.children, {
onDelete: this.handleMessageDelete
})}
</div>
)
}
}) Instead, use a componentless route and just do "normal" React stuff. <Route path="/inbox" component={Inbox}>
{/* no more `Message` */}
<Route path=":messageId"/>
</Route>
const Inbox = React.createClass({
render() {
const { messageId } = this.props.params
return (
<div>
{messageId ? (
<Message onDelete={this.handleMessageDelete}/>
) : (
<InboxStats/>
)}
</div>
)
}
})
|
Hmm, I'm trying to think about how that would apply in my situation. In this case, to be more specific, I'm trying to add a notification message that persists across containers: var AppBase = React.createClass({
getInitialState: function () {
return { flashes: [] };
},
addFlash: function (flash) {
this.setState({flashes: this.state.flashes.concat(flash)});
},
removeFlash: function (flash) {
this.setState({flashes: _.without(this.state.flashes, flash)});
},
render: function () {
return (
<div>
<Flash flashes={this.state.flashes} removeFlash={this.removeFlash} />
{React.cloneElement(this.props.children, {addFlash: this.addFlash})}
</div>
);
}
}); It looks to me like what you suggest makes sense when there's a particular resource that is being referenced, but I'm still not sure how it will work for periphery state like notifications or tracking loading state. |
Please use Stack Overflow or Reactiflux for support-related discussions. We want to keep the issue tracker and updates on issues to only touch on features and bugs. |
@underscorefunk You may want to have a look at Context. I just keep finding this issue when searching how to do this with React Router. I have routes set up like this: export const routes = {
<Route path="/" component={App}>
<Route path="/brands" lang="en" components={{mainComponent:BrandBrowser, appbarComponent:BrandBrowserAppBar}} />
<Route path="/merken" lang="nl" components={{mainComponent:BrandBrowser, appbarComponent:BrandBrowserAppBar}} />
</Route>
} What I am seeing is that my |
I didn't want to rely on context because of:
So far filtering props and passing them on down is working flawlessly. Also, I believe React router wants a single component to render... i.e. component={} as the prop... not components. Edit: Thanks to Download for pointing out Named Components! |
Multiple components is a feature called Named Components. |
This is essentially just syntactic sugar for the wrapper component solution suggested above by using an inline functional stateless component. This seems to be working for me so far.
|
A workaround is simply to pass in custom props by assigning them to render() {
// Pass account info to routes
this.props.children.props.account = this.state.account;
// Render dashboard
return (
<div className="app">
{this.props.children}
</div>
);
} You could implement a check for |
I have these code in my Root component: <Router history={ hashHistory }>
<Route path='/' component={ Main }>
<IndexRedirect to='detail' />
<Route path='detail' component={ Detail }/>
<Route path='comment' component={ CommentBox }/>
</Route>
</Router> and I want to pass some props to Main component, If I change the code like this: <Route path='/' component={ props => <Main data={...props}/> }> the browser would throw an error to told me that the props is undefined, even I change it to this: <Route path='/' component={ props => <Main/> }> the error still occurred. @prattl Any suggestion? Thanks. |
@eladnava That doesn't seem to work, I'm afraid. Even if it did, seems pretty hacky, no? |
@mismith might be slightly hacky, but it worked for me. If there is any cleaner way to do it without changing the entire router syntax / adding a middleware component, please share. |
I ended up just using what @Karthik248 proposed, e.g.: Not pretty, but seems like it's using the methods we're supposed to as they were intended. |
Isn't @epitaphmike 's solution the same as what @jdelight meant by using React.cloneElement? Just wondering why his comment elicited so many downvotes. That same solution received at least half as many votes when Mike gave it his own example, even though James also provided an example using it. Just a quick "wtf, community?!" moment that I felt needed to be pointed out. Carry on |
@mrjones91 It took he a few minutes to find the comments you were referring to. It's been about a year since I've looked at this thread. To what you are saying, I think most voters may have misunderstood what @jdelight was trying to say about passing props instead of state. But then again I cannot speak to their reasons. In my experience, when providing feedback in comments, (Which I rarely do, because of your exact point above) I always try to provide an example instead of linking to one. Your point is definitely noted though. I had never noticed the amount of downvotes that reply received. |
I'm reading through this, and it doesn't seem like the original question was ever answered (or if it was it's buried in the noise). The original question, as I understand it, is about declaratively exposing specific properties to a Route's child routes. Is that possible? For example, because of Lifting State I frequently end up with most of my 'smart' happening inside a parent component, with the child routes being mostly dumb. For example, say I'm editing user data across multiple pages.
My
Is there a way to only expose |
TL;DR The problem here, and in such similar cases, especially with the frameworks or libs written in some langs, a certain lack of means of combination (MoC). Primitives seems ok in React.js they are pretty good. With some already used syntax like; Otherwise, this problems of combinations of abstractions will recur and will need some less than optimal and indirect solutions called workarounds like wrapping etc, etc. Abstractions must be first class citizens as primitives, whatever the first class means. All IMHO. |
v4 this is easy stuff now.
I have no clue if this meets the MoC discussed above but I'm having a blast combining things with these means. |
Looks good, if only the v4 docs didn't error out so that I could read them @ryanflorence. On https://reacttraining.com/react-router/ - Chrome
FF
|
We just had a build misconfiguration earlier today, it's back up now. |
Thanks, but I'm still seeing the same errors even after a hard refresh and in incognito mode. I'll check back later. |
https://github.com/ReactTraining/react-router/tree/v4 Every package has a |
We are creating a tab system with We use Routes <Route path="user" component={UserContainer}>
<IndexRoute component={Welcome} />
<Route path="profile" component={UserProfile} />
<Route path="settings" component={UserSettings} />
</Route> In const TABS = {
PROFILE: 'profile',
SETTINGS: 'settings',
}
// ..
render () {
const activeTab = Object.values(TABS).find(tab => this.props.router.isActive(`/user/${tab}`))
const tabProps = (() => {
switch (activeTab) {
case TABS.SETTINGS: return { title: 'Settings' }
case TABS.PROFILE: return { title: 'Profile' }
default: return { title: 'Welcome' }
}
})()
return (
<div>
{React.cloneElement(this.props.children, tabProps}
</div>
)
}
I'm under the impression that all of this is a lot easier with v4, but we haven't upgraded yet. |
@ryanflorence Just to get this totally straight. Elaborating on your example:
The below two routes want to get both the router props and the app state/props. Is this legit way to do it?
Is there a way to pass props to Thank you very much for the router and the training! I have been holding out 'til now before using the router. The docs are great now, I tiny addition could be to mention the |
@janusch @ryanflorence I came here with the exact same question. Also agree that sending both props and router props to a component is a very common use case. For example, I'm updating the parent component's state and then passing that down to a child in the router, but need to send the params from the route to that child component as well. |
@janusch that's exactly how I do it. |
To be honest, I feel the Ideally, we should be able to serialize the routes to JSON and back and have them survive. Putting functions as props on routes breaks that. |
This issue is one of the best React Tutorials I've ever read. Thanks everyone. I didn't understand ~40% of the application/concepts though. Bookmarking for later after I've spent more than a week learning React. Someone with more experience needs to clean this up and contribute it to a wiki. |
I've been using the 0.13 version and taking a look at the version 1 beta. One thing that I would like to do with react-router is be more specific about passing props from a parent to a child via a Router Handler. I'm not sure if this is easier in version 1. This is what I was thinking...
In 0.13 you do this you pass props and state from a parent component to the RouterHandler like this:
This way, regardless of the route all props (and/or state) is passed down to any child component. The problem here is that those components may require different properties - they shouldn't be dependent on the shape of the parent's component.
I was thinking that the Route map itself could use a callback where the function parameters are the props/state of the parent component. This way the props can be shaped in the route map itself. It could also include the URL parameters as third function parameter.
This may be possible in v.1 in other ways (using some sort of wrapper component for example). Just highlighting a frustration I had with 0.13.
The text was updated successfully, but these errors were encountered: