fluxxor icon indicating copy to clipboard operation
fluxxor copied to clipboard

Cannot get reference to Router before rendering and can't add store without router reference

Open tachang opened this issue 8 years ago • 2 comments

I am trying to add a RouteStore but can't seem to get the router reference. I am running into issues where the Login component renders and detects the user is already logged in and shoots off an action to redirect but the Router store is not ready to receive it yet.

router = React.render((
  <Router history={new HashHistory} createElement={createFluxComponent}>
    <Route path="/" component={App}>
      <Route path="login" component={Login} />
      <Route path="logout" component={Logout} />
      <Route path="about" component={About} />
      <Route path="dashboard" component={Dashboard} onEnter={requireAuth} />
    </Route>
  </Router>
), document.getElementsByClassName('app')[0]);

// Need to add RouteStore here because I need a reference to router
flux.addStore("RouteStore", new RouteStore({router: router}));

When the Login component mounts though I do a check:

  componentWillMount: function() {

    var flux = this.getFlux();
    if( this.state.session ) {
      flux.actions.route.transition("dashboard");
    }
  },

However the Login component mounts before the Store can be added so I get

An action of type ROUTE_TRANSITION was dispatched, but no store handled it

tachang avatar Aug 31 '15 02:08 tachang

I'm not sure a route store is really the correct technique in most cases; the React Router example in the Fluxxor docs utilizes it mainly because models are not created asynchronously in action creators, unlike a more traditional Fluxxor app. Perhaps this sets a bad precedent.

Additionally, in React Router 1.0 beta, I'm not sure there's a way to access the router instance before the React rendering flow (via this.context), unlike in previous versions.

I think a more common technique, especially for situations like yours, would be to simply access the router on this.context to transition the URL (or just use the Navigation mixin):

  componentWillMount: function() {
    if( this.state.session ) {
      this.context.router.transitionTo("/dashboard");
    }
  },

  contextTypes: {
    router: React.PropTypes.object
  },

If you really want/need to utilize a store for routing operations, and just can't get ahold of the router instance, you might consider passing it along from the component:

  componentWillMount: function() {
    if( this.state.session ) {
      flux.actions.route.transition(this.context.router, "dashboard");
    }
  },

  contextTypes: {
    router: React.PropTypes.object
  },

Another (very hacky) workaround to the don't-have-the-router-instance-yet is to set it first thing from the componentWillMount of the very top-level component; something along the lines of:

  // In the App component
  componentWillMount: function() {
    var flux = this.getFlux();
    if (!flux.store("RouteStore")) {
      flux.addStore("RouteStore", new RouteStore({router: this.context.router}));
    }
  },

  contextTypes: {
    router: React.PropTypes.object
  },

Finally, I'm still not convinced the technique I originally used for the React Router example (before changing it) is actually that bad a practice; generally actions should be fire-and-forget, but for very side-effecty things like routing I'm not as much of a stickler:

  doThing: function() {
    flux.actions.thing.create(this.state.data, function(id) {
      this.context.router.transitionTo("/things/" + id);
    }.bind(this));
  },

  contextTypes: {
    router: React.PropTypes.object
  },

BinaryMuse avatar Aug 31 '15 03:08 BinaryMuse

In React Router 1.0 RC1, they show how you can get the history object:

Locations

Locations are now called histories (that emit locations). You import them from the history package, not react router.

// v0.13.x
Router.run(routes, Router.BrowserHistory, (Handler) => {
  React.render(<Handler/>, el);
})

// v1.0
import createBrowserHistory from 'history/lib/createBrowserHistory'
let history = createBrowserHistory()
React.render(<Router history={history}>{routes}</Router>, el)

This is analagous to getting a reference to the router before; You can use the history API to change the page. See also the API described at "Navigation Mixin".

BinaryMuse avatar Sep 17 '15 15:09 BinaryMuse