DEV Community

Cover image for Building a navigation drawer with Material UI and React Router DOM
Rossano D'Angelo
Rossano D'Angelo

Posted on • Edited on

23 4

Building a navigation drawer with Material UI and React Router DOM

In the last article, I built a basic Drawer that, at the end of this article, will contain a full working navigation block.

Installing React Router DOM

At the root folder of football-almanac, I run

npm install react-router-dom @types/react-router-dom
Enter fullscreen mode Exit fullscreen mode

It'll install React Router DOM.

Designing the navigation

First three URLs that come in mind for an application like this are

  • / (the home page)
  • /standings
  • /teams

Implementing React Router DOM

Wrap everything!

To get started, I import BrowserRouter in index.tsx and I wrap the whole application within it.

......
import { BrowserRouter } from 'react-router-dom';
......
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);
Enter fullscreen mode Exit fullscreen mode

At this point, since the App component is going to grow too much, I split it into multiple components. This will help me to ensure a good level of isolation of them, also for testing purposes.

The Router object

I find very helpful defining my routes as an object like this

const Routes = [
  {
    path: [url],
    sidebarName: [label],
    icon: [material_ui_icon_name],
    component: [component_name],
  },
  ...
];
Enter fullscreen mode Exit fullscreen mode

In this way, I can define my router once and reuse it when I need, as a module.

I define my routes in Routes.tsx.

import React from 'react';

const Home: React.FC = () => {
  return (
    <h1>Home</h1>
  );
};

const Standings: React.FC = () => {
  return (
    <h1>Standings</h1>
  );
};

const Teams: React.FC = () => {
  return (
    <h1>Teams</h1>
  );
};

const Routes = [
  {
    path: '/',
    sidebarName: 'Home',
    component: Home
  },
  {
    path: '/standings',
    sidebarName: 'Standings',
    component: Standings
  },
  {
    path: '/teams',
    sidebarName: 'Teams',
    component: Teams
  },
];

export default Routes;
Enter fullscreen mode Exit fullscreen mode

For the moment I create some placeholder components (Home, Standings and Teams).

The NavigationBar

I create a new component subfolder, named NavigationBar. The new component is NavigationBar.tsx.

import React, { useState } from 'react';

import { NavLink, withRouter } from 'react-router-dom';
import Routes from '../App/Routes';

import { createStyles, makeStyles, Theme } from '@material-ui/core/styles';
import {
  AppBar,
  Toolbar,
  Typography,
  IconButton,
  Drawer,
  MenuList,
  MenuItem,
  ListItemText,
 } from '@material-ui/core';
import MenuIcon from '@material-ui/icons/Menu';

const useStyles = makeStyles((theme: Theme) =>
  createStyles({
    root: {
      flexGrow: 1,
    },
    menuButton: {
      marginRight: theme.spacing(2),
    },
    title: {
      flexGrow: 1,
    },
    drawer: {
      width: 300,
    },
    fullList: {
      width: 'auto',
    },
  }),
);

const NavigationBar: React.FC = (props: any) => {
  const classes = useStyles();
  const [isOpen, setIsOpen] = useState(false);
  const toggleDrawer = (open: boolean) => (
    event: React.KeyboardEvent | React.MouseEvent,
  ) => {
    if (
      event.type === 'keydown' &&
      ((event as React.KeyboardEvent).key === 'Tab' ||
        (event as React.KeyboardEvent).key === 'Shift')
    ) {
      return;
    }

    setIsOpen(open);
  };

  const activeRoute = (routeName: any) => {
    return props.location.pathname === routeName ? true : false;
  }

  return (
    <div>
      <div className={classes.root}>
        <AppBar position="static">
          <Toolbar>
            <IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu" onClick={toggleDrawer(true)}>
              <MenuIcon />
            </IconButton>
            <Typography variant="h6" className={classes.title}>
              Football Almanac
            </Typography>
          </Toolbar>
        </AppBar>
      </div>
      <Drawer classes={{ paper: classes.drawer }} open={isOpen} onClose={toggleDrawer(false)}>
        <div
          className={classes.fullList}
          role="presentation"
          onClick={toggleDrawer(false)}
          onKeyDown={toggleDrawer(false)}
        >
          <MenuList>
            {Routes.map((prop, key) => {
              return (
                <NavLink to={prop.path} style={{ textDecoration: 'none' }} key={key}>
                  <MenuItem selected={activeRoute(prop.path)}>
                    <ListItemText primary={prop.sidebarName} />
                  </MenuItem>
                </NavLink>
              );
            })}
          </MenuList>
        </div>
      </Drawer>
    </div>
  );
};

export default withRouter(NavigationBar);
Enter fullscreen mode Exit fullscreen mode

To have the browser navigation available within this component, I used a higher-order component that comes with React Router DOM, withRouter.

It passes updated match, location, and history props to the wrapped component whenever it renders.

To learn more about withRender, take a look at the documentation.

App.tsx

import React from 'react';

import { Switch, Route } from 'react-router-dom';
import Routes from './Routes';

import NavigationBar from './NavigationBar/NavigationBar';

const App: React.FC = () => {
  return (
    <div>
      <NavigationBar />
      <Switch>
        {Routes.map((route: any) => (
          <Route exact path={route.path} key={route.path}>
            <route.component />
          </Route>
        ))}
      </Switch>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Understanding the following snippet is essential: since we can easily add and remove routes from an independent module, it's enough iterating it and create a Route for each route defined in the object.

<Switch>
  {Routes.map((route: any) => (
    <Route exact path={route.path} key={route.path}>
      <route.component />
    </Route>
  ))}
</Switch>
Enter fullscreen mode Exit fullscreen mode

The result is the following

Alt Text

What's next

In the next step I will create the home page that will display some data fetched from the APIs.

Useful resources

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (1)

Collapse
 
abhisheksharmas04 profile image
abhisheksharmas04

could you please tell.. how to create drawer navigation using javascript syntax in react js.