React-router: Grab active route using react router v4

Created on 21 Mar 2017  路  8Comments  路  Source: ReactTraining/react-router

First of all, thanks for working on React Router v4. I like the changes applied in the new version, really clean API, really well done.

I work on Grommet, an UX framework that runs on top of ReactJS and supports React Router.

Recently I tried to start our migration to support react router v4. In the past we used to be completely un-opinionated about routing. But recently we added support for React Router inside our Anchor component. We do not use Link or NavLink, we use a prop called path to identify when to use React router and when not to use it.

React router Anchor: <Anchor path="/test" icon={<PreviousIcon />} />
Anchor without React Router: <Anchor href="/test" icon={<PreviousIcon />} />

I'm calling history.push and history.replace in a very similar way that React Router <Link /> does.

The problem I'm facing with v4 is that this.context.router.isActive is completely gone. I tried to find a reason why, or some migration guide, without success. Here is our current implementation of the Anchor that uses React v3:

https://github.com/grommet/grommet/blob/master/src/js/components/Anchor.js#L23

Well, I tried looking into the new <NavLink /> implementation and it seems now things are very JSX-driven. From what I understood from this component it seems that you are declaring a new <Route /> to be able to get a match in the callback. Initially this seems odd to me as I'm not creating a new Route but trying to identify if a given path is active or not.

Could you please explain why router.isActive is gone and what would be a way to get the active route without using JSX for that?

Most helpful comment

All 8 comments

Rendering a <Route> does not necessarily mean "only render when you match the current location". For example, it can be used inject the router variables from the context into a component as props.

In <NavLink>, the <Route> is using the children prop, which means that it will call the children function whether or not the route matches. If the match is null, then we know that it did not match.

If you would prefer not to use <Route children> in this way, React Router offers an imperative approach with the matchPath function. This is what <Switch>es and <Route>s use internally to match a location against a path.

Thanks let me try that right now 馃憤 appreciate the explanation.

I tried but it is always returning the root path.

screen shot 2017-03-20 at 9 13 49 pm

As you can see when the path is /dashboard I would expect the return to be that, and not /.

Here is how I'm describing the Routes:

<Router>
        <App centered={false}>
          <Split priority={priority} flex='right'
            onResponsive={this._onResponsive}>
            {nav}
            <div>
              <Route exact={true} path='/' component={Dashboard} />
              <Route path='/login' component={Login} />
              <Route path='/dashboard' component={Dashboard} />
              <Route exact={true} path='/tasks' component={Tasks} />
              <Route path='/tasks/:id' component={Task} />
            </div>
          </Split>
        </App>
      </Router>

By inspecting how matchPath works, it expects you to send the path as an option, otherwise it will use / by default.

In v3 isActive module receives the existing routes:

https://github.com/ReactTraining/react-router/blob/v3/modules/isActive.js#L125

and I don't think matchPath does that.

I'm not actually sure where you are logging.

Anyways, this is the matchPath implementation I was referring to. The important thing to remember is that partial matches will be "active", so if you only want the path to be "active" when it completely matches, you will need to pass the exact: true option to matchPath or check the returned match object's isExact property.

let active = false
const { router } = this.context
const { path } = this.props
if (path && router) {
  const { location } = router
  active = matchPath(location.pathname, { path }) != null
}
this.setState({ active })

Thanks this works!

using context is usually not a good idea I am having this problem right now and kinda found a work around . i'll paste some code here .

export default () => (
  <Router>
    <div>
      <Route path="/:active?" component={Nav} />
      <div className="container">
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
      </div>
    </div> 
  </Router>
)

here I am rendering nav on every every path basically and giving it a nameactive and making it optional for home

now I can check for match.params.active in nav component it gives the full path .
heres an example

        <div className="nav navbar-nav" >
          <li className={!match || !match.params || !match.params.active ? "active" : ""}><NavLink to="/">Home</NavLink></li>
          <li className={match.params.active === 'about' ? "active" : ""}><NavLink to="/about">About</NavLink></li>
          <li className={match.params.active === 'notfound' ? "active" : ""}><NavLink to="/notfound">nothing</NavLink></li>
        </div>
Was this page helpful?
0 / 5 - 0 ratings

Related issues

maier-stefan picture maier-stefan  路  3Comments

stnwk picture stnwk  路  3Comments

andrewpillar picture andrewpillar  路  3Comments

jzimmek picture jzimmek  路  3Comments

ackvf picture ackvf  路  3Comments