-
-
Notifications
You must be signed in to change notification settings - Fork 10.6k
Send params to all active routes #158
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
Comments
I'm not sure I agree. For your use case, you should be doing this: <Route handler={User}>
<Route path="user/:userId/details" handler={Details}/>
<Route path="user/:userId/activity" handler={Activity}/>
<!-- This route has access to params.userId, and can act accordingly -->
<Route path="user/:userId" handler={RedirectSomewhere}/>
</Route> I guess I'd just like to see a better use case. This one seems to be solved nicely by #159. |
We also have an example where we have an account section wrapper that adds a sidebar of account links an a page title of the account page you are viewing. Rather than having to render the title on every account page, we want to show it in the wrapper. But in order for that to happen, we need to access a title from the active route component (think it makes more sense to define in there rather than in the wrapper based on route name) in the wrapper route. For example: |
I should also point out that I really don't think If it did care about |
I guess the part that doesn't really make sense to me is parent routes that care about segments of the URL they're not even sure exist. I'm not totally opposed to having one set of params per transition (instead of per route). It certainly would simplify the implementation. Just feels a little messy, that's all. This conversation reminds me of the "Thinking in React" post, particularly the part about identifying where state should live. |
I agree that my UI wrappers don't need paths defined for them. Instead, I'd expect to add another route child inside it for special cases that did redirects (like @mjackson showed). But there are definitely times where it would be nice to have a way to easily access data in the child/active route from the UI wrapper route. Otherwise, I just end up adding a component or mixin to all of the account pages to handle it and make the UI wrapper component do less. |
@mjackson three things
|
I wonder if <Route name="user" path="user/:userId" handler={User}>
<Route name="details" path="user/:userId/details" handler={Details}/>
<Route name="activity" path="user/:userId/activity" handler={Activity}/>
<Redirect path="user/:userId" to="details"/>
</Route> Still feels weird configuring a path for |
@rpflorence I agree with you; For the use case being described here, @bobeagan Your use case sounds interesting, but I'm having a hard time following it from your description. Care to share the relevant portion of your route config? |
@mjackson you're forgetting that the var User = React.createClass({
render: function() {
return (
<div>
<h2>{this.state.user.name}</h2>
<this.props.activeRouteHandler/>
</div>
);
}
}); |
I'm also curious about how to handle my example #158 (comment) where the content that the UI wrapper needs to display is not directly linked to the params but rather the component. Maybe there is an existing, preferred approach to this which I'm just unfamiliar with? |
@rpflorence Why don't you just have the var Activity = React.createClass({
render: function () {
return (
<div>
<Sidebar user={this.state.user}/>
<p>The activity page.</p>
</div>
);
}
}); User-specific UI doesn't need to live in the |
@mjackson why have the router do any UI nesting at all then? Just match urls to handlers w/o nesting and then share all UI with components like that. shared wrapper UI is the whole point of this router The params are the params, I can see no reason you'd ever not want the parent route to have them. The only reason is "well, it doesn't have a path that asks for it" which a really dumb reason. |
Our original implementation did this, and I didn't do it just because the implementation was easier, I did it because I wanted my parent UI, that never matches a path, to be able to read the params. I was actually surprised last week to find out it didn't work this way anymore. |
@rpflorence nailed it. We're getting two things that should be separate (path matching w/ params, and UI nesting) and mixing them. Stepping through the guidelines in "Thinking in React":
I've got my
Done;
Can't; At this point I'm questioning the sanity of having UI nesting and routing bound so closely together, but I'm tired, so it may just be that. In fact; what's the point of routes that have no path? They're not really routes, just objects that wrap routes. That's more like layout inheritance than routes. Here's a crazy idea:
If a Rolling back on my crazy idea, this would also get closer to a workable solution:
... in this case, |
Ember doesn't have this ambiguity because every route has a path and is a target. It also introduces "index routes" that are child routes that render when a parent has no active children. Though its confusing originally to most people, it makes sense eventually. This also only really works well when paths are inherited from parent to child. I still maintain we should just pass params to every active handler, but there is definitely a conversation here about routes w/o paths and why they are called "routes" anyway. A thought I'm not sure I'm loving yet, but seems to solve this problem, is to require all routes to have a path, and introduce "Default" routes. They are just like routes except they render when no child matches the parent's path. <Routes>
<Route name="user" path="user/:userId" handler={User}>
<DefaultRoute handler={Details}/>
<Route name="activity" path="user/:userId/activity" handler={Activity}/>
</Route>
</Routes>
// or if you really want /details in the url
<Routes>
<Route name="user" path="user/:userId" handler={User}>
<DefaultRoute handler={RedirectToDetails}/>
<Route name="details" path="user/:userId/details" handler={Details}/>
<Route name="activity" path="user/:userId/activity" handler={Activity}/>
</Route>
</Routes>
// and maybe if we keep going down the `<Redirect/>` path
<DefaultRedirect to="details" /> |
Thanks for the explanations @rpflorence and @JedWatson. Let's go ahead and make the change to pass the active params to all handlers. @rpflorence Let's continue the convo about routes w/o paths in another issue. |
@rpflorence that looks like a more explicit version of my second idea, where we detect the I generally don't want the If we're being explicit, I'm not sure it's worth another type; wouldn't it be cleaner to specify a My $0.02 is the first is cleaner; it saves validating that there should only be one default child per Also, maybe important to note that the solution should behave recursively, so it can scale to multiple levels of nested default routes, for more complicated use-cases than our example. |
@JedWatson were I do build this UI it would look something like this: <Routes>
<Route name="user" path="user/:userId" handler={User}>
<Route name="activity" path="user/:userId/activity" handler={Activity}/>
</Route>
</Routes> var User = React.createClass({
// code to fetch the user, probably with `AsyncState`
render: function() {
return (
<div>
<h1>{this.state.user.name}</h1>
{this.props.activeRouteHandler() || Details({user: this.state.user})}
</div>
);
}
}); The constraint that made things more complicated was having the |
I started digging into the implementation of this feature this morning and realized there's a problem. It has to do with transition hooks. The way the router works currently, we know exactly which params each route is interested in. They are declared in the <Route handler={User}>
<Route path="users/:userID/activity" handler={Activity}/>
<Route path="users/:userID/details" handler={Details}/>
</Route> I know that Now, let's say the user clicks on a link from On the other hand, if we know what params tl;dr If we pass all params to every route handler then every transition triggers @rpflorence Turns out "well, it doesn't have a path that asks for it" isn't "a really dumb reason" after all. :P |
Anyway, my recommendation would be that if you need a param, declare it in your This config solves the transition hooks issue: <Route path="users/:userID" handler={User}>
<Route path="users/:userID/activity" handler={Activity}/>
<Route path="users/:userID/details" handler={Details}/>
</Route> It also eliminates the need for Edit: Or use |
@mjackson and I had a quick chat in IRC. Only sending params to routes that care is important for transitions so I'm closing this issue. Though you can solve the problem already as we've shown in the last few commnets, we'd like it to be more obvious how to meet this use-case. So, we'll be adding It will look something like this, and remove the need to check if there's a valid <!-- render details at `/users/123` -->
<Routes>
<Route name="user" path="user/:userId" handler={User}>
<Route name="activity" path="user/:userId/activity" handler={Activity}/>
<DefaultRoute handler={Details}/>
</Route>
</Routes>
<!-- redirect to `/users/123/details` from `/users/123` -->
<Routes>
<Route name="user" path="user/:userId" handler={User}>
<Route name="activity" path="user/:userId/activity" handler={Activity}/>
<Route name="details" path="user/:userId/details" handler={Details}/>
<DefaultRoute handler={RedirectToDetails}/>
</Route>
</Routes> |
Just thinking about this a little more. Here's what these APIs would look like without <!-- render details at `/users/123` -->
<Routes>
<!-- use this.props.activeRouteHandler() || Details() inside User#render -->
<Route name="user" path="user/:userId" handler={User}>
<Route name="activity" path="user/:userId/activity" handler={Activity}/>
</Route>
</Routes>
<!-- redirect to `/users/123/details` from `/users/123` -->
<Routes>
<Route name="user" path="user/:userId" handler={User}>
<Route name="activity" path="user/:userId/activity" handler={Activity}/>
<Route name="details" path="user/:userId/details" handler={Details}/>
<!-- Use <Redirect> and avoid need to create RedirectToDetails -->
<Redirect path="user/:userId" to="details"/>
</Route>
</Routes> Of these two, the first is definitely the stronger use case IMO. We could solve it using a <!-- render details at `/users/123` -->
<Routes>
<Route name="user" path="user/:userId" handler={User} defaultRouteHandler={Details}>
<Route name="activity" path="user/:userId/activity" handler={Activity}/>
</Route>
</Routes> In this case, we would automatically set render: function () {
return this.props.activeRouteHandler();
} regardless of whether the path is |
hmm ... what if I want to send "static" props to the (I like that the path only lives in one place and there's no explaining to do about inheriting paths of parents) |
Then ignore |
I LIKE THIS |
except ... that it is rendered as a sibling to the other routes but doesn't look like them ... |
(I still like it) however ... as we keep talking about making |
Is there any reason not to just have the current params available on
this.props.params
in every active handler?Sometimes you want a parent route to nest some UI in that depends on the params to get the data, but is not actually a valid path itself.
For instance, we want to support
user/123/details
anduser/123/activity
, and have them share a UI wrapper, but we do not want the urluser/123
to match anything (we'd probably redirect somewhere).To make this work we have to put a path on the user route, and then redirect in
componentWillMount
if there is noactiveRouteHandler
.#149
The text was updated successfully, but these errors were encountered: