Search code examples
reactjsmaterial-uireact-routernavigation

How to highlight and get active state for main navigation menu item and sub navigation menu item on the same page using React Material UI Tabs


In my React <Header> component I use Material UI Tabs to create a primary menu for the main navigation, with react router links instead of showing tab content:

    <Tabs value={location.pathname}>
      {(items || []).map((item) => (
        <Tab
          key={item.url}
          label={item.title}
          value={item.url}
          href={item.url}
          disableRipple
        />
      ))}
    </Tabs>

On the same page I use Material UI Tabs again for a secondary menu.

The route path to the main navigation menu item is: /example-path. When I navigate to this route, this menu items is highlighted and has active state.

When the tab of secondary menu item with the same route as the highlighted primary menu item /example-path, both menu items gets highlighted and active state.

When I click on another secondary menu item only this secondary menu item gets highlighted /example-path/tab-two.

How do I manage both gets highlighted in the same time, The parent from main navigation and the various menu items from the secondary menu?


Solution

  • The Header component should match on only the root path segment while the Submenu component can compare the complete URL pathname. Note that if you've further sub-routes then Submenu would need to check the first two segments.

    Instead of the Tab components rendering a raw anchor tag <a> it should render a Link component. This is so the router can respond to and handle the navigation action instead of letting the browser make a page request to the server and reload the entire app.

    Header

    import { Tabs, Tab } from "@material-ui/core";
    import { Link, useLocation } from "react-router-dom";
    
    const Header = () => {
      const { pathname } = useLocation();
      const base = `/${pathname.slice(1).split("/").shift()}`;
    
      return (
        <Tabs value={base}>
          <Tab component={Link} to="/" label="Home" key="/" value="/" />
          <Tab
            component={Link}
            to="/about"
            label="About"
            key="/about"
            value="/about"
          />
          <Tab
            component={Link}
            to="/dashboard"
            label="Dashboard"
            key="/dashboard"
            value="/dashboard"
          />
        </Tabs>
      );
    };
    
    export default Header;
    

    Submenu

    import { Tabs, Tab } from "@material-ui/core";
    import { Link, useLocation } from "react-router-dom";
    
    const Submenu = () => {
      const { pathname } = useLocation();
    
      return (
        <Tabs value={pathname}>
          <Tab
            component={Link}
            to="/about/about-one"
            label="About sub one"
            key="1"
            value="/about/about-one"
          />
          <Tab
            component={Link}
            to="/about/about-two"
            label="About sub two"
            key="2"
            value="/about/about-two"
          />
        </Tabs>
      );
    };
    
    export default Submenu;
    

    App Order the routes within the Switch in inverse order of path specificity so that more specific paths are matched before falling back to less specific paths.

    <Router>
      <Layout>
        <Switch>
          <Route path="/about/about-one">
            <AboutOne />
          </Route>
          <Route path="/about/about-two">
            <AboutTwo />
          </Route>
          <Route path="/about">
            <About />
          </Route>
          <Route path="/dashboard">
            <Dashboard />
          </Route>
          <Route path="/">
            <Home />
          </Route>
        </Switch>
      </Layout>
    </Router>
    

    Edit how-to-highlight-and-get-active-state-for-main-navigation-menu-item-and-sub-navi

    enter image description here

    Update to handle submenu tabs on the root "/" route. For this you'll need to do a bit of a "hack" to check that the URL path is not one of the other main root routes, so that you can override the base Tabs value to be exactly "/" instead of one of the "/home-section/*" sub-routes, using the matchPath utility function.

    Example:

    import { Tabs, Tab } from "@material-ui/core";
    import { Link, useLocation, matchPath } from "react-router-dom";
    
    const Header = () => {
      const { pathname } = useLocation();
      const base = `/${pathname.slice(1).split("/").shift()}`;
    
      const isNotHome = ["/about", "/dashboard"].some((path) =>
        matchPath(pathname, path)
      );
    
      return (
        <Tabs value={isNotHome ? base : "/"}>
          <Tab component={Link} to="/" label="Home" key="1" value="/" />
          <Tab component={Link} to="/about" label="About" key="2" value="/about" />
          <Tab
            component={Link}
            to="/dashboard"
            label="Dashboard"
            key="3"
            value="/dashboard"
          />
        </Tabs>
      );
    };
    
    export default Header;