5
votes

I'm trying to get the current path of the react router in a container so I can pass it to a child component that will change it's visibility filter.

More specifically, I'm trying to make a navigation menu highlight the currently active page.

I'm using react, redux, react-router, and react-router-redux so I can access the router state from the redux store.

From the docs for react-router-redux, it says to do something like this:

function mapStateToProps(state, ownProps) {
  return {
    id: ownProps.params.id,
    filter: ownProps.location.query.filter
  };
}

Here is my container component:

import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { Link } from 'react-router'
import {
  Segment as UISegment,
} from 'semantic-ui-react'
import NavMenu from '../components/NavMenu'

class MenuBar extends Component {
  static propTypes = {
    path: PropTypes.string.isRequired
  }

  render() {
    const { path, } = this.props

    return (
      <UISegment>
        <NavMenu activePath={path} />
      </UISegment>
    )
  }
}

const mapStateToProps = (state, ownProps) => {   
  return {
    path: ownProps.route ? ownProps.route.path : "/"
  }
}

export default connect(mapStateToProps)(MenuBar)

Inside the NavMenu component, a semantic-ui menu component will compare activePath with its own path and highlight the active button.

Everything seems to work in theory; when I click on the different parts of the menu, a @@router/LOCATION_CHANGE action is emitted. In the redux dev tools, I see the state changing. However, mapStateToProps is never called and this component is never re-rendered.

Any ideas? I thought about using the react methods like shouldComponentUpdate, but it seems that react doesn't even realize the state or props are changing.

1
to change a state in redux, you need to do it through dispatch.Rei Dien

1 Answers

4
votes

First thing to note is that you are not actually accessing router state from the store. If you look at the react-router-redux docs, it actually warns against doing so

You should not read the location state directly from the Redux store. This is because React Router operates asynchronously (to handle things such as dynamically-loaded components) and your component tree may not yet be updated in sync with your Redux state. You should rely on the props passed by React Router, as they are only updated after it has processed all asynchronous code.

Your container is reading data from ownProps, which is just the props that are passed into that container component. The example in the react-router-redux docs that you are referencing only works for a top-level route component (a component that is passed as the component prop to a React Router Route component). React Router passes the router data into all route components.

In your case, MenuBar is a child of whatever your top level route component is. Your two options are to

  1. Pass the data you want into MenuBar down from your route component.
  2. Use React Router's withRouter higher order component to inject the values into MenuBar https://github.com/ReactTraining/react-router/blob/v3/docs/API.md#withroutercomponent-options

Also, I believe the value you are looking for is ownProps.location.pathname rather than ownProps.route.path

Some code for option 1, since I'm assuming MenuBar isn't nested too deeply in your component tree:

If your route config is

<Router history={browserHistory}>
  <Route path="/" component={AppLayout}>
    <Route path="about" component={About}/>
    <Route path="users" component={Users}/>
    <Route path="*" component={NoMatch}/>
  </Route>
</Router>

your AppLayout would be something like

const AppLayout = ({ children, location }) => {
  return (
    <div>
      <MenuBar path={ location.pathname } />
      { children }
    </div>
  )
} 

and MenuBar would receive the data your are looking for.