React-router: Route change event

Created on 17 Mar 2015  路  33Comments  路  Source: ReactTraining/react-router

Hey,
I need to add a route change event handler within my route definition.

AuthModule/Routes.js

Router.createRoute({ path: /login, name: "auth-login", handler: App, parentRoute: route }, function(){
        Router.createDefaultRoute({ handler: Login });
        Router.createRoute({ path: "/register", name: "auth-register", handler: Register });
    });

// This is what I need
Router.on("transition", function(from, to, cancel){
// e.g. if !loggedIn && to.name.indexOf("auth-") != 0 -> cancel
})

Using statics on handlers is not a solution (avoid boilerplate), also using Router.Run to track route changes isn't my preferred solution (not modular). Is there anything available like this?

Thanks in advance!

Most helpful comment

Thanks for the fast reply @KeitIG, I'm not sure if this is supposed to be internal but I discovered a more convenient approach:

browserHistory.listen(function(ev) {
  console.log('listen', ev.pathname);
});

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

All 33 comments

Thanks for your help, though I knew of this solution. I really prefer to let my router decide what location needs authentication instead of the component. As I use the programmatic API, this solution suits my needs:

var transitionHandler = function(transition) {
       // could also check transition.path
            if (!AuthStore.isAuthenticated()) {
                transition.redirect('login', {}, {nextPath: transition.path});
            }
        }

Router.createRoute({ name: "route1" , onEnter: transitionHandler });
Router.createRoute({ name: "route2" , onEnter: transitionHandler });

Hi,

There is way to use "onEnter" with JSX?

+1 looking for answers on previous question from @calbertts

This example: https://github.com/rackt/react-router/blob/master/examples/auth-flow/app.js#L129 suggests to me that you can. I'm not having luck making it work though, so I have the same question as @calbertts.

+1

I have the same question as @calbertts (I couldn't get the example at https://github.com/rackt/react-router/blob/master/examples/auth-flow/app.js#L129 to work)

+1 same here
(I couldn't get the example at https://github.com/rackt/react-router/blob/master/examples/auth-flow/app.js#L129 to work)

I manage to make the auth example work by adding "super()," in the constructors.

class App extends React.Component {
constructor () {
super(),
this.state = {
loggedIn: auth.loggedIn()
};
} ......

What about this issue now?

master (forthcoming 1.0 API) has hooks on routes, check out the auth example and the docs

@ryanflorence Not sure you read the discussion above, this issue concerns the onEnter callback added in version 1.0

Same question as @mcobzarenco

As the doc seems to be for 1.0 and a lot of us still uses 0.13.3, a lot of examples do not work. onEnter and onLeave on <Route> do not trigger anything.

If you're using 0.13.x, you'll want to be sure to reference the 0.13.x docs. I think we've got a broken link from the 0.13.x branch README that unfortunately sends you to docs for 1.0.x. We'll fix that.

In the meantime, you can find docs for 0.13 here: https://github.com/rackt/react-router/tree/0.13.x/doc

For detecting route change within a component (e.g. if just the URL parameter changes) I've managed to get change detection working with a mixin:

module.exports = {
  componentWillMount: function() {
    this.setState({
      _routePath: this.getPath(),
    });
  },

  componentWillUpdate: function() {
    var newPath = this.getPath();

    if (this.state._routePath !== newPath) {
      this.setState({
        _routePath: newPath,
      });

      if (this.onRouteChanged) {
        this.onRouteChanged();
      }
    }
  },
};

Then in your component simply add a onRouteChanged method to handle the route change.

@hiddentao Do you have any idea why this is being triggered twice every route change?

Edit: You should use "componentWillReceiveProps" instead of "componentWillUpdate"

@frg Thanks for that. If onRouteChanged is being called twice then my guess is that the setState() call isn't being processed in time (thus causing this.state._routePath to still be the old path) before the second time the lifecycle method gets invoked - thus causing onRouteChanged to be called again.

I do it like this, ataching event to _Location_:

componentDidMount() {
    this.context.router.getLocation().addChangeListener(listener);
}
componentWillUnmount() {
    this.context.router.getLocation().removeChangeListener(listener);
}

using _Router.HistoryLocation_

Router.run(routes, Router.HistoryLocation, (Handler, state) => { ... });

Since a few people above have asked about onEnter in jsx and I didn't see an answer, this appears to work just fine to me:

function temp() {
  console.log('Hi');
}

const routes = (
    <Route path='/' component={Index}>
      <Route path='path1' component={comp1} onEnter={temp} />
      <Route path='path2' component={comp2} onEnter={temp} />
      <Route path='*' component={FourZeroFour}/>
    </Route>);

@AndrX your solution is great. Saved me a bunch of trouble.

There was a problem with your code, though, so I made a few modifications and ended up with this:

componentDidMount() {
  this.context.router.listen(this.locationHasChanged)
}

componentWillUnmount() {
  this.context.router.unregisterTransitionHook(this.locationHasChanged)
}

Works beautifully.

Yeah onEnter is fine now since react-router 1.0.

Is there a way to listen to all route changes? For some reason my windows popstate event isn't being fired (perhaps it's being consumed by react-router?) and onChange/onUpdate doesn't seem to work on the top level route? Thanks

@DominicTobias You can call the same function for all routes and use a callback to distinguish each route if you need a custom behavior.

Thanks for the fast reply @KeitIG, I'm not sure if this is supposed to be internal but I discovered a more convenient approach:

browserHistory.listen(function(ev) {
  console.log('listen', ev.pathname);
});

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

Looks better indeed :)

Please open a question on Stack Overflow and we'll answer it there.

@DominicTobias Thanks for that, I went with your solution. With a large number of routes it is not practical to add the callback to each one and also the onEnter function could get quite busy if you want to use it for authentication, authorisation etc.

If someone is still troubled with this issue I am using a custom solution with Eventemitter.
See the files here
The relevant files are:

  • subviews/Addurl.js
  • helpers/mrEmitter.js
  • app/Sidebar.js
  • app/Main.js

If you are looking for a small example see this

Any reason why browserHistory.listen is not documented?

@DominicTobias I've spent all my day trying to find a way do handle route changes. THANKS A LOT!

I test and give you this:

console.log('this.context.router is same as this.props.router', this.context.router === this.props.router); //true
console.log('this.context.router is same as browserHistory', this.context.router === browserHistory);   //false
console.log('this.context.router.push is same as browserHistory.push', this.context.router.push === browserHistory.push);   //true

Tried to find a method for this as well and ended up here, but I just realized I already have it:

<Router history={browserHistory} onUpdate={this.doStuffOnUpdate}>....</Router>

Maybe I'm interpreting it wrong, but this seems to work fine for me, ref: https://github.com/ReactTraining/react-router/blob/master/docs/API.md#onupdate

try to implement HOC (Higher order component), you can take reference below code

import React,{Component,PropTypes} from 'react';
import {connect} from 'react-redux';

export default function (ComposedComponent) {
  class Authentication extends Component{
    static contextTypes={
      router:React.PropTypes.object
    }

    componentWillMount(){
     console.log(this.props.authenticatedUser);
      let token = localStorage.getItem('jwtToken');
      if(this.props.authenticatedUser==='logout' || token==='undefined'){
        this.context.router.push('/login');
      }
    }

    componentWillUpdate(nextProps){
      console.log( 'tt ' +nextProps.authenticatedUser);
      let token = localStorage.getItem('jwtToken');
      if(nextProps.authenticatedUser==='logout' || token==='undefined'){
        this.context.router.push('/login');
      }
    }

    render(){
      return(<ComposedComponent {...this.props} />)
    }
  }

  function mapStateToProps(state) {
    return {
      authenticatedUser: state.user.status,
      user: state.user
    };
  }
  return connect(mapStateToProps)(Authentication) ;
}

I need to do something before the route change. Is there any before route change event?
browserHistory.listen handler is fired just immediately after route changes.

@vnsrahul1304, try browserHistory.listenBefore

Was this page helpful?
0 / 5 - 0 ratings