Skip to content
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

Support for dynamic routes #243

Closed
ryanatkn opened this issue Aug 29, 2014 · 22 comments
Closed

Support for dynamic routes #243

ryanatkn opened this issue Aug 29, 2014 · 22 comments

Comments

@ryanatkn
Copy link

Is dynamically creating routes based on application state a supported or planned feature? This jsfiddle shows an example of props being resolved only the first time. Perhaps there's a better pattern I haven't found.

The use case I'm after is explained in the docs for react-router-component, which supports dynamic routes:

Having routes defined as a part of your component hierarchy allows to dynamically reconfigure routing based on your application state. For example you can return a different set of allowed locations for anonymous and signed-in users.

@ryanflorence
Copy link
Member

We believe that you should declare all your routes up front, and then validate the visitor's ability to visit them like the auth-flow example.

I'm sure you could setup/teardown some initial routes, make your decision, and then render some new ones though.

@ryanatkn
Copy link
Author

@rpflorence Do you have a recommendation for the following use case?

<Route handler={App}>
  <Route name="session-user-page" path={"/" + sessionUser.name} handler={SessionUserPage}/>
  <Route name="public-user-page" path="/:userName" handler={PublicUserPage}/>
</Route>

I could change it to this -

<Route handler={App}>
  <Route name="user-page" path="/:userName" handler={UserPage}/>
</Route>
// `UserPage` matches `sessionUser` against the route param and loads either `SessionUserPage` or `PublicUserPage`

but then the App would give every handler the sessionUser prop whether it uses it or not - which doesn't seem problematic, just not very clean. The alternative, hooking all handlers that need the sessionUser into the store, would be a lot of code duplication.

Thanks for the help!

@ryanflorence
Copy link
Member

<Route handler={App}>
  <Route name="session-user-page" path={"/" + sessionUser.name} handler={SessionUserPage}/>
  <Route name="public-user-page" path="/:userName" handler={PublicUserPage}/>
</Route>

That will work fine, as long as you always have a value in sessionUser.name.

I'd personally rather have one route and check a session store to branch in UserPage

but then the App would give every handler the sessionUser prop

Not sure I understand why you think this is the case. Any component can talk to a session store without passing it down through props.

var moduleThatKnowsTheSession = require('../stores/session');
var UserPage = React.createClass({
  // some code to get state from the module that knows the session
  render: function() {
    if (!this.state.session.loggedIn) {
      return <PublicUser/>
    } else {
      return <SessionUser/>
    }
  }
});
module.exports = UserPage;

@ryanatkn
Copy link
Author

The problem I ran into with that bit of code not working is that props are only resolved the first render (as shown in the jsfiddle), so passing the sessionUser as a prop meant it never got updated in the handler, even after re-rendering the routes. So I was unable to tear down and re-render routes like you suggested - although it's likely I'm missing something.

I assumed that passing thesessionUser as a prop would be closer to the recommended React best practices. I wanted to avoid setting up multiple store listeners throughout the component hierarchy, instead opting for a small number of "ControllerViews" that the React devs have talked about, like the top-level App that would listen to the session store. Nested listeners ended up causing unmounted components to cause invariants on store changes, but maybe that's the best pattern and I need to check mounted status. It seems like an anti-pattern to forego nested store listeners, directly access store state, and rely on the hierarchy to propagate changes, therefore I came back around to the idea of passing props. There are a lot of possible solutions but at the moment none seem very elegant to me. This is a problem that extends beyond react-router, but it first hit me when trying to pass props to routes based on application state, which I couldn't make work as shown in the jsfiddle.

@ryanflorence
Copy link
Member

If your session data is going to change, then yeah, that won't work.

  • every route handler is a controller-view
  • route handlers are entry points into your app, therefore doing anything you would feel good about doing in App, you should feel good about doing in a nested handler, no matter how deep
  • you should remove change listeners in componentWillUnmount

Finally, server apps don't have dynamic routes, not sure why we'd expect a client app to.

@ryanatkn
Copy link
Author

Makes sense - thanks for the help!

It looks like my problem with the nested listeners is due to the inability to remove listeners during an emit call in node's EventEmitter implementation, but that seems like reasonable behavior, so I'll just check for isMounted. I'm going to try having nested components listen for changes as described here.

  1. Now all your data is in stores, but how do you get it into the specific component that needs it? We started with large top level components which pull all the data needed for their children, and pass it down through props. This leads to a lot of cruft and irrelevant code in the intermediate components. What we settled on, for the most part, is components declaring and fetching the data they need themselves, except for some small, more generic components. Since most of our data is fetched asynchronously and cached, we've created mixins that make it easy to declare which data your component needs, and hook the fetching and listening for updates into the lifecycle methods (componentWillMount, etc).

@ryanflorence
Copy link
Member

Yeah, that's how I do it :)

@zackfern
Copy link

@rpflorence I was wondering if you would have any suggestions for creating Routers that are based off of other files that are required at runtime? I'm working on a project which will be a platform that future developers will create plugins to run on, so they need to be able to create their React components, and then register them with the Router. This makes it so there won't be an opportunity for us to declare all routes up front, as you mentioned earlier.

My current thought process is that each component that will be providing a Route "registers" into an object and then we iterate over those registers routes when creating the object. Unfortunately I haven't had any luck so far. Any suggestions?

@ryanflorence
Copy link
Member

you can "export" some routes from a plugin maybe?

<Routes>
  {SomePlugin.routes()}
</Routes>

@emmenko
Copy link

emmenko commented Jan 29, 2015

@zackfern any luck on your problem? I also need to implement some similar solution for a plugin system and I am also struggling with the setup. If you could shred some light or if you found some solution it would be awesome, thanks ;)

@revolunet
Copy link

hey :) i'd like to handle infinite nested states in my app, for example i open a "card" and another one can open inside it, and on and on, on-demand.
So all these states cannot be predicted, they depend on the user interactions, ex :

technology > javascript > web > css...

any idea how to implement this in a clean way ?

@pawawat
Copy link

pawawat commented May 19, 2015

@zackfern have you got a solution on your problem? I have the similar problem, thanks

@ryanflorence
Copy link
Member

use a wild-card path, and then parse it yourself.

this.props.params.splat

And then nest the Card view over and over for your parsed path.

On Tue, May 19, 2015 at 10:20 AM, pawawat notifications@github.com wrote:

@zackfern https://github.com/zackfern have you got a solution on your
problem? I have the similar problem, thanks


Reply to this email directly or view it on GitHub
#243 (comment).

@zackfern
Copy link

@pawawat I ended up creating my own registration system for registration into the routes. When it came time to generate the Route JSX I looped over my registration array and generated the Route object on the fly.

@revolunet
Copy link

@zackfern any code sample ?

@vcarl
Copy link

vcarl commented May 21, 2015

I'm in a weird spot where I'd love support for this, but only so that we can phase it out little by little in the existing application. We're using backbone routes, with subroutes being dynamically added when a route is loaded--which is super gross. I'm pushing for moving to react-router, but having to migrate all at once makes the task a lot more imposing.

@bishopZ
Copy link

bishopZ commented Oct 25, 2015

I too have this issue where my routes are stored in an immutable map. Even converting the map to an array, react-router still does not resolve the routes.

ReactDOM.render(
  <Router history={history}>
      {state.pages.toSeq().toArray().map((item) =>
        <Route 
          key={item.get('route')} 
          path={item.get('route')} 
          component={require(item.get('component'))} />
      )}
  </Router>
  , target
);

I think that using the "*" route and doing our own custom parsing will work for us in the short-term, but another solution would be better. Any help appreciated.

@rhzs
Copy link

rhzs commented Jan 12, 2016

any update on @bishopZ code? i also can't do the dynamic routes with map.

@10thfloor
Copy link

Adding dynamic routes using map would be a great feature. The comment about server-side applications not supporting dynamic routes seems a bit dismissive; there is no reason I can see for enforcing the same paradigm in browser apps.

Use case: I'm building a quiz app where each question gets a route (on client), and users can add questions (by pushing an object into an array), thereby (in a perfect world) adding routes to access those questions, via map ... same need as @bishopZ

What are the drawbacks I'm not seeing? Would this be difficult to implement?

@kanedaki
Copy link

kanedaki commented May 9, 2016

Dynamic routes would be also very helpful when loading async modules that also have some routes.

@taion
Copy link
Contributor

taion commented May 9, 2016

Dynamic routes are supported for the async case. You just can't change your root route ATM.

@eloiqs
Copy link

eloiqs commented May 18, 2016

I had a problem while trying to generate routes based on my state. I'm dumping that here in case someone stumble upon this post like I did earlier.

const routes = [
  this.state.routes.map(
    route => {
        {
          path: route.path,
          component: route.component,
        }
    }
  )
]

and later when you declare your router:

<Router routes={routes} history={browserHistory} />

from the docs

@lock lock bot locked as resolved and limited conversation to collaborators Jan 21, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests