8

I'm trying to implement the BottomNavigation Component from Material UI and i have an issue with when the user uses the back and forward buttons of the browser.

The problem is, that the BottomNavigation Component is configured to change page in the the layout when a navigation item button is pressed. What it doesn't do however, is change the selected index of the BottomNavigation items when the browser is used to go back to the previous page.

What im left with is an inconsistent state.

How do i go about changing the selected index of the navigation component when the browser buttons are used?

Here is how the UI looks :-

[Material UI Layout[1]

Here is the Root Component :-

import React from 'react'
import {browserHistory, withRouter} from 'react-router';
import {PropTypes} from 'prop-types'
import {connect} from 'react-redux'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import AppBar from 'material-ui/AppBar'
import Paper from 'material-ui/Paper'
import MyBottomNavigation from '../material-ui/MyBottomNavigation'

const style = {
    padding: 10,
    height: '85vh'

  }

class Root extends React.Component {
    constructor(props) {
        super(props)   

        this.state  = {
            bottomNavItemIndex : 0
        }
    }


    render() {
        return (
            <MuiThemeProvider>
                <div>
                    <AppBar title="Pluralsight Admin" 
                        iconClassNameRight="muidocs-icon-navigation-expand-more"
                        showMenuIconButton={false}
                        zDepth={1}
                        />
                    <Paper zDepth={1} style={style}>
                        {this.props.children}
                    </Paper>
                    <MyBottomNavigation/>
                </div>
            </MuiThemeProvider>

        )
    }
}


Root.propTypes = {
    children: PropTypes.object.isRequired
}



export default Root

And here is the Navigation Component :-

class MyBottomNavigation extends Component {
    constructor(props){
        super(props)
        this.state = {
            selectedIndex: 0
          }

        this.selectBottomNavigationItem = this.selectBottomNavigationItem.bind(this)
    }

  selectBottomNavigationItem(index){
    this.setState({selectedIndex: index})

    switch(index) {
        case 0:
        return browserHistory.push('/')
        case 1:
        return browserHistory.push('/courses')
        case 2:
        return browserHistory.push('/authors')
        default:
        return browserHistory.push('/')
    }
  }


  render() {
    return (
      <Paper zDepth={1}>
        <BottomNavigation selectedIndex={this.state.selectedIndex}>

          <BottomNavigationItem
            label="Home"
            icon={recentsIcon}
            onClick={() => this.selectBottomNavigationItem(0)}
          />

          <BottomNavigationItem
            label="Course"
            icon={favoritesIcon}
            onClick={() => this.selectBottomNavigationItem(1)}
          />

          <BottomNavigationItem
            label="Authors"
            icon={nearbyIcon}
            onClick={() => this.selectBottomNavigationItem(2)}
          />
        </BottomNavigation>
      </Paper>
    )
  }
}


export default MyBottomNavigation
Derek
  • 7,530
  • 9
  • 49
  • 80

3 Answers3

11

Just got an implementation working!

The trick is to make a new navbar component that wraps the Material UI BottomNavigation and exports it with the react-router-dom's withRouter higher order function. Then you can do some fiddling with the current route passed into the props and set the value of the BottomNavigation component based on an array of routes (which route corresponds to which value).

My code works a bit differently than what you posted originally, I'm just going off of the BottomNavigation example here and the example of usage with react-router-dom here.

Here is my implementation:

/src/App.js
import React, {Component} from 'react';
import './App.css';
import {BrowserRouter as Router, Route} from 'react-router-dom';
import PrimaryNav from './components/PrimaryNav';

// Views
import HomeView from './views/HomeView';

class App extends Component {
  render() {
    return (
      <Router>
        <div className="app">
          <Route path="/" component={HomeView} />        
          <PrimaryNav />
        </div>
      </Router>
    );
  }
}

export default App;
/src/components/PrimaryNav.js
import React, {Component} from 'react';
import {Link, withRouter} from 'react-router-dom';
import BottomNavigation from '@material-ui/core/BottomNavigation';
import BottomNavigationAction from '@material-ui/core/BottomNavigationAction';
import LanguageIcon from '@material-ui/icons/Language';
import GroupIcon from '@material-ui/icons/Group';
import ShoppingBasketIcon from '@material-ui/icons/ShoppingBasket';
import HelpIcon from '@material-ui/icons/Help';
import EmailIcon from '@material-ui/icons/Email';
import './PrimaryNav.css';

class PrimaryNav extends Component {
  state = {
    value: 0,
    pathMap: [
      '/panoramas',
      '/members',
      '/shop',
      '/about',
      '/subscribe'
    ]
  };

  componentWillReceiveProps(newProps) {
    const {pathname} = newProps.location;
    const {pathMap} = this.state;

    const value = pathMap.indexOf(pathname);

    if (value > -1) {
      this.setState({
        value
      });
    }
  }

  handleChange = (event, value) => {
    this.setState({ value });
  };

  render() {
    const {value, pathMap} = this.state;

    return (
      <BottomNavigation
        value={value}
        onChange={this.handleChange}
        showLabels
        className="nav primary"
      >
        <BottomNavigationAction label="Panoramas" icon={<LanguageIcon />} component={Link} to={pathMap[0]} />
        <BottomNavigationAction label="Members" icon={<GroupIcon />} component={Link} to={pathMap[1]} />
        <BottomNavigationAction label="Shop" icon={<ShoppingBasketIcon />} component={Link} to={pathMap[2]} />
        <BottomNavigationAction label="About" icon={<HelpIcon />} component={Link} to={pathMap[3]} />
        <BottomNavigationAction label="Subscribe" icon={<EmailIcon />} component={Link} to={pathMap[4]} />
      </BottomNavigation>
    );
  }
}

export default withRouter(PrimaryNav);

And here's my version numbers for good measure:

"@material-ui/core": "^1.3.1",
"@material-ui/icons": "^1.1.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
Let Me Tink About It
  • 11,866
  • 13
  • 72
  • 169
saricden
  • 1,382
  • 2
  • 20
  • 34
1

Just found a really neat solution for this here:

Essentially you just create a pathname constant each render, using window.location.pathname and make sure that the value prop of each <BottomNavigationAction /> is set to the same as the route (including preceding forward slash) ...something like:

const pathname = window.location.pathname
const [value, setValue] = useState(pathname)
const onChange = (event, newValue) => {
    setValue(newValue);
}
return (
    <BottomNavigation className={classes.navbar} value={value} onChange={onChange}>
        <BottomNavigationAction component={Link} to={'/'} value={'/'} label={'Home'} icon={<Home/>} />
        <BottomNavigationAction component={Link} to={'/another-route'} value={'/another-route'} label={'Favourites'} icon={<Favorite/>} />
    </BottomNavigation>
)

This means the initial state for value is always taken from the current URL.

nihilok
  • 322
  • 1
  • 8
0

I think you should avoid internal state management for this component. If you need to know and highlight the current selected route, you can just use NavigationLink from react-router-dom instead of Link. An "active" class will be added to the corresponding element. You just need to pay attention for the exact prop if you want the navigation element to be active only when an exact match is detected.

[Update] I wrote the wrong component name that is NavLink, not NavigationLink. My bad. Here is the link to the doc https://reactrouter.com/web/api/NavLink