What's the best way to separate the routes in react-router-dom?

Viewed 1871

I did a small task with routes, in which when I put / in URL it redirects me to the login page and when I put /dashboard it redirects me to dashboard having material UI persistent drawer. But I did it the completely wrong way as I used browserRouter in 2 components but ideally, it should be only in the root component and because of this when I click on some link in my drawer for the first time it renders that component in the dashboard but when I refresh the page it loads nothing I saw an example here routes are quite well organized like when I click on login their URL is auth/login when I signup their URL is auth/signup and the same goes for the dashboard when I go to dashboard their URL is dashboard/app and when I click on other link items the pattern goes same dashboard/link.

Please see the above example to understand what I am trying to explain.

You can see my code here on codesandbox too

Inshort: I want to organize my routes in such a way auth/login auth/signup dashboard/app dashboard/profile etc.

My App.js

import { Switch, Route } from "react-router-dom";

import AppDrawerBar from "./compponents/AppDrawerBar";
import Login from "./pages/Login";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <Switch>
        <Route exact path="/" exact component={Login} />
        <Route path="/dashboard" component={AppDrawerBar} />
      </Switch>
    </div>
  );
}

Login.js

import React from "react";

const Login = () => {
  return <h1>Login Page</h1>;
};

export default Login;

Dashboard.js where links contain routes for home component by default and about compoenet

import React from "react";
import clsx from "clsx";
import { makeStyles, useTheme } from "@material-ui/core";
import Drawer from "@material-ui/core/Drawer";
import CssBaseline from "@material-ui/core/CssBaseline";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import List from "@material-ui/core/List";
import Typography from "@material-ui/core/Typography";
import Divider from "@material-ui/core/Divider";
import IconButton from "@material-ui/core/IconButton";
import MenuIcon from "@material-ui/icons/Menu";
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import ListItem from "@material-ui/core/ListItem";
import ListItemIcon from "@material-ui/core/ListItemIcon";
import ListItemText from "@material-ui/core/ListItemText";
import InboxIcon from "@material-ui/icons/MoveToInbox";
import MailIcon from "@material-ui/icons/Mail";
import Home from "../pages/Home";
import About from "../pages/About";
import { BrowserRouter, Link, Route, Switch } from "react-router-dom";

const drawerWidth = 240;

const useStyles = makeStyles((theme) => ({
  root: {
    display: "flex"
  },
  appBar: {
    transition: theme.transitions.create(["margin", "width"], {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.leavingScreen
    })
  },
  appBarShift: {
    width: `calc(100% - ${drawerWidth}px)`,
    marginLeft: drawerWidth,
    transition: theme.transitions.create(["margin", "width"], {
      easing: theme.transitions.easing.easeOut,
      duration: theme.transitions.duration.enteringScreen
    })
  },
  menuButton: {
    marginRight: theme.spacing(2)
  },
  hide: {
    display: "none"
  },
  drawer: {
    width: drawerWidth,
    flexShrink: 0
  },
  drawerPaper: {
    width: drawerWidth
  },
  drawerHeader: {
    display: "flex",
    alignItems: "center",
    padding: theme.spacing(0, 1),
    // necessary for content to be below app bar
    ...theme.mixins.toolbar,
    justifyContent: "flex-end"
  },
  content: {
    flexGrow: 1,
    padding: theme.spacing(3),
    transition: theme.transitions.create("margin", {
      easing: theme.transitions.easing.sharp,
      duration: theme.transitions.duration.leavingScreen
    }),
    marginLeft: -drawerWidth
  },
  contentShift: {
    transition: theme.transitions.create("margin", {
      easing: theme.transitions.easing.easeOut,
      duration: theme.transitions.duration.enteringScreen
    }),
    marginLeft: 0
  }
}));

export default function PersistentDrawerLeft() {
  const classes = useStyles();
  const theme = useTheme();
  const [open, setOpen] = React.useState(true);

  const handleDrawerOpen = () => {
    setOpen(true);
  };

  const handleDrawerClose = () => {
    setOpen(false);
  };

  return (
    <BrowserRouter>
      <div className={classes.root}>
        <CssBaseline />
        <AppBar
          position="fixed"
          className={clsx(classes.appBar, {
            [classes.appBarShift]: open
          })}
        >
          <Toolbar>
            <IconButton
              color="inherit"
              aria-label="open drawer"
              onClick={handleDrawerOpen}
              edge="start"
              className={clsx(classes.menuButton, open && classes.hide)}
            >
              <MenuIcon />
            </IconButton>
            <Typography variant="h6" noWrap>
              Persistent drawer
            </Typography>
          </Toolbar>
        </AppBar>
        <Drawer
          className={classes.drawer}
          variant="persistent"
          anchor="left"
          open={open}
          classes={{
            paper: classes.drawerPaper
          }}
        >
          <div className={classes.drawerHeader}>
            <IconButton onClick={handleDrawerClose}>
              {theme.direction === "ltr" ? (
                <ChevronLeftIcon />
              ) : (
                <ChevronRightIcon />
              )}
            </IconButton>
          </div>
          <Divider />
          <List>
            <ListItem button key="home" to="/home" component={Link}>
              <ListItemIcon>
                <MailIcon />
              </ListItemIcon>
              <ListItemText primary="Home" />
            </ListItem>
          </List>
          <Divider />
          <List>
            <ListItem button key="about" to="/about" component={Link}>
              <ListItemIcon>
                <InboxIcon />
              </ListItemIcon>
              <ListItemText primary="About" />
            </ListItem>
          </List>
        </Drawer>
        <main
          className={clsx(classes.content, {
            [classes.contentShift]: open
          })}
        >
          <div className={classes.drawerHeader} />
          <Route path="/home" exact component={Home} />
          <Route path="/about" component={About} />
        </main>
      </div>
    </BrowserRouter>
  );
}

Home.js

import React from "react";

const Home = () => {
  return <h1>Home Page</h1>;
};

export default Home;

About.js

import React from "react";

const About = () => {
  return <h1>About Page</h1>;
};

export default About;

1 Answers

If I understand your question correctly, you are trying to create nested routes, i.e. you want the About component rendered on a nested route in the "/dashboard" sub-domain, like "/dashboard/about".

Issues

You've wrapped the AppDrawerBar component into a second, nested Router. This can mess with the overall app routing as the outer router wrapping the app isn't made aware of anything the nested inner routers are handling. It also doesn't nest the route paths.

Solution

  1. Create a new Dashboard component to act as a "landing" page and manage nested paths. You'll want to also move the AppDrawerBar here since that is what handles your nested route navigation.

    import { Switch, Redirect, Route, useRouteMatch } from "react-router-dom";
    import Home from "./Home";
    import About from "./About";
    import AppDrawerBar from "../components/AppDrawerBar";
    
    const Dashboard = () => {
      const { path } = useRouteMatch();
      return (
        <>
          <AppDrawerBar>
            <Switch>
              <Route path={`${path}/about`} component={About} />
              <Route path={`${path}/home`} exact component={Home} />
              <Redirect to={`${path}/home`} />
            </Switch>
          </AppDrawerBar>
        </>
      );
    };
    
    export default Dashboard;
    
  2. Don't wrap AppDrawerBar with another Router and remove the Route components defined there. Instead, render passed children. Update the links to use url from the current route match to compute the nested links.

    function PersistentDrawerLeft({ children }) {
      ...
    
      const { url } = useRouteMatch();
    
      ...
    
      return (
        <div className={classes.root}
    
          ...
    
          <Drawer
            className={classes.drawer}
            variant="persistent"
            anchor="left"
            open={open}
            classes={{
              paper: classes.drawerPaper
            }}
          >
            <div className={classes.drawerHeader}>
              <IconButton onClick={handleDrawerClose}>
                {theme.direction === "ltr" ? (
                  <ChevronLeftIcon />
                ) : (
                  <ChevronRightIcon />
                )}
              </IconButton>
            </div>
            <Divider />
            <List>
              <ListItem button key="home" to={`${url}/home`} component={Link}>
                ...
              </ListItem>
            </List>
            <Divider />
            <List>
              <ListItem button key="about" to={`${url}/about`} component={Link}>
                ...
              </ListItem>
            </List>
          </Drawer>
          <main
            className={clsx(classes.content, {
              [classes.contentShift]: open
            })}
          >
            <div className={classes.drawerHeader} />
            {children}
          </main>
        </div>
      );
    }
    

Demo

Edit whats-the-best-way-to-separate-the-routes-in-react-router-dom

Related