2

Ok so I have read through other articles about the react router changing the url but not the component on here, but they are all from over 3 years ago. I didn't start having this issue until React version 18 came out.

I created a browser touter instance in my App.js and have tried just doing it in the index.js but neither works. I wrap a browserRouter and Routes around the Route elements in the App.js and then in a Nav.js component I use window.location.href = "/" forhte routes, but the when you run the app and click on the navbar, the url changes the specified route, but the component does not change.

Here is the index.js, App.js, and the Nav.js code:

index.js:

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

App.js

import { Routes, Route, BrowserRouter } from "react-router-dom";
import Nav from "./components/Nav";
import Home from "./pages/Home";
import About from "./pages/About";

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route exact path="/" element={<Nav />}>
          <Route exact index element={<Home />} />
          <Route exact path="/about" element={<About />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

Nav.js

import * as React from "react";
import AppBar from "@mui/material/AppBar";
import Box from "@mui/material/Box";
import Toolbar from "@mui/material/Toolbar";
import IconButton from "@mui/material/IconButton";
import Typography from "@mui/material/Typography";
import Menu from "@mui/material/Menu";
import MenuIcon from "@mui/icons-material/Menu";
import Container from "@mui/material/Container";
import Avatar from "@mui/material/Avatar";
import Tooltip from "@mui/material/Tooltip";
import MenuItem from "@mui/material/MenuItem";

import png from "../img/done.png";

const settings = ["Profile", "Account", "Dashboard", "Logout"];

const Nav = () => {
  const [anchorElNav, setAnchorElNav] = React.useState(null);
  const [anchorElUser, setAnchorElUser] = React.useState(null);

  const handleOpenNavMenu = (event) => {
    setAnchorElNav(event.currentTarget);
  };
  const handleOpenUserMenu = (event) => {
    setAnchorElUser(event.currentTarget);
  };

  const handleCloseNavMenu = () => {
    setAnchorElNav(null);
  };

  const handleCloseUserMenu = () => {
    setAnchorElUser(null);
  };

  const goHome = () => {
    window.location.href = "/";
    handleCloseNavMenu();
  };
  const goAbout = () => {
    window.location.href = "/about";
    handleCloseNavMenu();
  };

  return (
    <AppBar position="fixed">
      <Container maxWidth="xl">
        <Toolbar disableGutters>
          <Typography
            variant="h6"
            noWrap
            component="div"
            sx={{ mr: 2, display: { xs: "none", md: "flex" } }}
          >
            <img style={{ height: "4em" }} src={png} alt="done." />
          </Typography>

          <Box sx={{ flexGrow: 1, display: { xs: "flex", md: "none" } }}>
            <IconButton
              size="large"
              aria-label="account of current user"
              aria-controls="menu-appbar"
              aria-haspopup="true"
              onClick={handleOpenNavMenu}
              color="inherit"
            >
              <MenuIcon />
            </IconButton>
            <Menu
              id="menu-appbar"
              anchorEl={anchorElNav}
              anchorOrigin={{
                vertical: "bottom",
                horizontal: "left",
              }}
              keepMounted
              transformOrigin={{
                vertical: "top",
                horizontal: "left",
              }}
              open={Boolean(anchorElNav)}
              onClose={handleCloseNavMenu}
              sx={{
                display: { xs: "block", md: "none" },
              }}
            >
              <MenuItem onClick={() => goHome()}>Home</MenuItem>
              <MenuItem onClick={() => goAbout()}>About</MenuItem>
            </Menu>
          </Box>
          <Typography
            variant="h6"
            noWrap
            component="div"
            sx={{ flexGrow: 1, display: { xs: "flex", md: "none" } }}
          >
            done.
          </Typography>
          <Box sx={{ flexGrow: 1, display: { xs: "none", md: "flex" } }}>
            <MenuItem onClick={() => goHome()}>Home</MenuItem>
            <MenuItem onClick={() => goAbout()}>About</MenuItem>
          </Box>

          <Box sx={{ flexGrow: 0 }}>
            <Tooltip title="Open settings">
              <IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
                <Avatar alt="Remy Sharp" src="/static/images/avatar/2.jpg" />
              </IconButton>
            </Tooltip>
            <Menu
              sx={{ mt: "45px" }}
              id="menu-appbar"
              anchorEl={anchorElUser}
              anchorOrigin={{
                vertical: "top",
                horizontal: "right",
              }}
              keepMounted
              transformOrigin={{
                vertical: "top",
                horizontal: "right",
              }}
              open={Boolean(anchorElUser)}
              onClose={handleCloseUserMenu}
            >
              {settings.map((setting) => (
                <MenuItem key={setting} onClick={handleCloseUserMenu}>
                  <Typography textAlign="center">{setting}</Typography>
                </MenuItem>
              ))}
            </Menu>
          </Box>
        </Toolbar>
      </Container>
    </AppBar>
  );
};

export default Nav;

I am using Material UI just as background knowledge, but I tried doing it with just a basic <ul><li><a></a></li></ul> navbar but it made no difference.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181
PrismaticDevs
  • 75
  • 2
  • 7
  • It looks like you're using react-router-dom v6 but v6 doesn't support the `exact` attribute anymore. Here's another answer on it: https://stackoverflow.com/questions/69866581/property-exact-does-not-exist-on-type – arfi720 May 01 '22 at 02:00
  • Thanks for the suggestion but I removed exact and changed it to just ```}> } /> } /> ``` to no avail – PrismaticDevs May 01 '22 at 04:00

2 Answers2

1

Issues

You are rendering the app routes as children of the route rendering the Nav component.

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route exact path="/" element={<Nav />}>
          <Route exact index element={<Home />} />
          <Route exact path="/about" element={<About />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

The issue is that the Nav component isn't rendering an Outlet component for the nested routes to be matched and rendered into.

You are also incorrectly using window.location.href to issue imperative navigation actions. These will unnecessarily reload the page, i.e. remount the entire React application. Use the navigate function or render Link components if you want to navigate around your app.

See Outlet

Solution

Fix the window.location.href = XXX issues in Nav component. I suggest importing and using the useNavigate hook to access the navigate function.

import * as React from "react";
import { useNavigate } from 'react-router-dom'; // <-- import useNavigate hook
import {
  AppBar,
  Avatar,
  Box,
  Container,
  IconButton,
  Menu,
  MenuIcon,
  MenuItem,
  Toolbar,
  Tooltip,
  Typography,
} from "@mui/material";
import png from "../img/done.png";

const settings = ["Profile", "Account", "Dashboard", "Logout"];

const Nav = () => {
  const navigate = useNavigate(); // <-- invoke hook

  const [anchorElNav, setAnchorElNav] = React.useState(null);
  const [anchorElUser, setAnchorElUser] = React.useState(null);

  const handleOpenNavMenu = (event) => {
    setAnchorElNav(event.currentTarget);
  };
  const handleOpenUserMenu = (event) => {
    setAnchorElUser(event.currentTarget);
  };

  const handleCloseNavMenu = () => {
    setAnchorElNav(null);
  };

  const handleCloseUserMenu = () => {
    setAnchorElUser(null);
  };

  const goHome = () => {
    handleCloseNavMenu();
    navigate("/");       // <-- issue imperative navigation
  };
  const goAbout = () => {
    handleCloseNavMenu();
    navigate("/about");  // <-- issue imperative navigation
  };

  return (
    <AppBar position="fixed">
      <Container maxWidth="xl">
        <Toolbar disableGutters>
          <Typography
            variant="h6"
            noWrap
            component="div"
            sx={{ mr: 2, display: { xs: "none", md: "flex" } }}
          >
            <img style={{ height: "4em" }} src={png} alt="done." />
          </Typography>

          <Box sx={{ flexGrow: 1, display: { xs: "flex", md: "none" } }}>
            <IconButton
              size="large"
              aria-label="account of current user"
              aria-controls="menu-appbar"
              aria-haspopup="true"
              onClick={handleOpenNavMenu}
              color="inherit"
            >
              <MenuIcon />
            </IconButton>
            <Menu
              id="menu-appbar"
              anchorEl={anchorElNav}
              anchorOrigin={{
                vertical: "bottom",
                horizontal: "left",
              }}
              keepMounted
              transformOrigin={{
                vertical: "top",
                horizontal: "left",
              }}
              open={Boolean(anchorElNav)}
              onClose={handleCloseNavMenu}
              sx={{
                display: { xs: "block", md: "none" },
              }}
            >
              <MenuItem onClick={goHome}>Home</MenuItem>
              <MenuItem onClick={goAbout}>About</MenuItem>
            </Menu>
          </Box>
          <Typography
            variant="h6"
            noWrap
            component="div"
            sx={{ flexGrow: 1, display: { xs: "flex", md: "none" } }}
          >
            done.
          </Typography>
          <Box sx={{ flexGrow: 1, display: { xs: "none", md: "flex" } }}>
            <MenuItem onClick={goHome}>Home</MenuItem>
            <MenuItem onClick={goAbout}>About</MenuItem>
          </Box>

          <Box sx={{ flexGrow: 0 }}>
            <Tooltip title="Open settings">
              <IconButton onClick={handleOpenUserMenu} sx={{ p: 0 }}>
                <Avatar alt="Remy Sharp" src="/static/images/avatar/2.jpg" />
              </IconButton>
            </Tooltip>
            <Menu
              sx={{ mt: "45px" }}
              id="menu-appbar"
              anchorEl={anchorElUser}
              anchorOrigin={{
                vertical: "top",
                horizontal: "right",
              }}
              keepMounted
              transformOrigin={{
                vertical: "top",
                horizontal: "right",
              }}
              open={Boolean(anchorElUser)}
              onClose={handleCloseUserMenu}
            >
              {settings.map((setting) => (
                <MenuItem key={setting} onClick={handleCloseUserMenu}>
                  <Typography textAlign="center">{setting}</Typography>
                </MenuItem>
              ))}
            </Menu>
          </Box>
        </Toolbar>
      </Container>
    </AppBar>
  );
};

export default Nav;

You've a couple of options to get the nested routes working:

  1. Convert Nav to a layout component and route that renders an Outlet component.

    export default function App() {
      return (
        <BrowserRouter>
          <Routes>
            <Route path="/" element={<Nav />}>
              <Route index element={<Home />} />
              <Route path="about" element={<About />} />
            </Route>
          </Routes>
        </BrowserRouter>
      );
    }
    

    ...

    import * as React from "react";
    import { Outlet, useNavigate } from 'react-router-dom'; // <-- import Outlet
    ...
    
    const Nav = () => {
      ...
    
      return (
        <>
          <AppBar position="fixed">
            <Container maxWidth="xl">
              <Toolbar disableGutters>
                ...
              </Toolbar>
            </Container>
          </AppBar>
          <Outlet /> // <-- render Outlet for nested routes
        </>
      );
    };
    
    export default Nav;
    
  2. Remove the "/" layout route rendering the Nav component, move Nav out of the Routes component, and convert the index route to path="/".

    export default function App() {
      return (
        <BrowserRouter>
          <Nav />
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
          </Routes>
        </BrowserRouter>
      );
    }
    
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
0

I have updated all the dependencies of my template to the latest versions, and from now on in react 18 you have to use the hook called useNavigate to trigger component changes. This is such a hurtle! I'm using redux and I've implemented redux-first-routing a long time ago. Until now, I've had just to push the pathname into the store to have a component change trigger. But now you have to add extra code to trigger the component change or add force reload to createBrowserHistory. What doesn't work well with spa. Until now I've not found a way to override the push action so that it includes component change trigger. And using useNavigate to only trigger the component change is redux user unfriendly! They have to provide something else besides of hook based component change.