You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Material-UI中ProSidebar双击激活路由的问题排查

解决React Router侧边栏菜单项需双击激活的问题

我在使用React Router的React应用中遇到导航问题:必须双击侧边栏菜单项才能使其激活并触发对应路由。已确认路由配置正确,但问题依旧。

App组件代码

import { ColorModeContext, useMode } from "./theme";
import { CssBaseline, ThemeProvider } from "@mui/material";
import { Routes, Route } from "react-router-dom";
import Topbar from "./scenes/global/Topbar";
import Sidebar from "./scenes/global/Sidebar";
import Beranda from "./scenes/beranda";
import SiswaTerdaftar from "./scenes/siswaTerdaftar";

function App() {
  const [theme, colorMode] = useMode();

  return (
    <ColorModeContext.Provider value={colorMode}>
      <ThemeProvider theme={theme}>
        <CssBaseline />
        <Sidebar />
        <div style={{ height: "100%", width: "100%" }}>
          <Topbar />
          <main className="content">
            <Routes>
              <Route path="/" element={<Beranda />} />
              <Route path="/siswaterdaftar" element={<SiswaTerdaftar />} />
            </Routes>
          </main>
        </div>
      </ThemeProvider>
    </ColorModeContext.Provider>
  );
}

export default App;

Sidebar组件代码

import * as React from "react";
import { useState } from "react";
import {
  ProSidebar,
  Menu,
  MenuItem,
  SidebarFooter,
  SidebarContent,
  SidebarHeader,
} from "react-pro-sidebar";
import { Box, IconButton, Button, Typography, useTheme } from "@mui/material";
import { Link } from "react-router-dom";
import "react-pro-sidebar/dist/css/styles.css";
import { tokens } from "../../theme";
import MenuRoundedIcon from "@mui/icons-material/MenuRounded";
import HomeRoundedIcon from "@mui/icons-material/HomeRounded";
import InventoryRoundedIcon from "@mui/icons-material/InventoryRounded";
import HowToRegRoundedIcon from "@mui/icons-material/HowToRegRounded";
import logoSMK from "../../assets/images/smk-informatika.png";
import BasicDialogs from "../../components/common/Modal/dialog";
import DaftarUlangForm from "../modal/DaftarUlangForm";

function Copyright(props) {
  return (
    <Typography
      variant="body2"
      color="text.secondary"
      align="center"
      {...props}
    >
      {"Copyright © "}
      <Link color="inherit" href="https://mui.com/">
        Tentackle
      </Link>{" "}
      {new Date().getFullYear()}
      {"."}
    </Typography>
  );
}

const Item = ({ title, to, icon, selected, setSelected }) => {
  const theme = useTheme();
  const colors = tokens(theme.palette.mode);
  return (
    <MenuItem
      active={selected === title}
      style={{
        color: colors.grey[200],
      }}
      onClick={() => setSelected(title)}
      icon={icon}
    >
      <Typography fontWeight="medium">{title}</Typography>
      <Link to={to} />
    </MenuItem>
  );
};

const Sidebar = () => {
  const theme = useTheme();
  const colors = tokens(theme.palette.mode);
  const [isCollapsed, setIsCollapsed] = useState(false);
  const [selected, setSelected] = useState("Beranda");
  const [open, setOpen] = React.useState(false);

  return (
    <Box
      sx={{
        position: "sticky",
        height: "100vh",
        top: 0,
        bottom: 0,
        "& .pro-sidebar-inner": {
          background: `${colors.primary[400]} !important`,
        },
        "& .pro-icon-wrapper": {
          backgroundColor: "transparent !important",
        },
        "& .pro-inner-item": {
          padding: "5px 35px 5px 20px !important",
        },
        "& .pro-inner-item:hover": {
          color: "#4389a9 !important",
        },
        "& .pro-menu-item.active": {
          color: "#4389a9 !important",
        },
      }}
    >
      <ProSidebar breakPoint="md" collapsed={isCollapsed}>
        <SidebarHeader>
          <Menu iconShape="square">
            {/* LOGO AND MENU ICON */}
            <MenuItem
              onClick={() => setIsCollapsed(!isCollapsed)}
              icon={isCollapsed ? <MenuRoundedIcon /> : undefined}
              style={{
                margin: "10px 0 0 0",
                color: colors.grey[100],
              }}
            >
              {!isCollapsed && (
                <Box
                  display="flex"
                  justifyContent="space-between"
                  alignItems="center"
                  ml="20px"
                >
                  <Box
                    display="flex"
                    justifyContent="center"
                    alignItems="center"
                  >
                    <img
                      alt="profile-user"
                      width="30px"
                      height="30px"
                      src={logoSMK}
                      style={{ cursor: "pointer", borderRadius: "50%" }}
                    />
                  </Box>
                  <Typography
                    variant="h4"
                    fontWeight="bold"
                    color={colors.blueAccent[400]}
                  >
                    Admin PPDB
                  </Typography>
                  <IconButton onClick={() => setIsCollapsed(!isCollapsed)}>
                    <MenuRoundedIcon />
                  </IconButton>
                </Box>
              )}
            </MenuItem>
          </Menu>
        </SidebarHeader>
        <SidebarContent>
          <Menu>
            <Box paddingLeft={isCollapsed ? undefined : "10%"}>
              <Item
                title="Beranda"
                to="/"
                fontWeight="bold"
                icon={<HomeRoundedIcon />}
                selected={selected}
                setSelected={setSelected}
              />
              <Item
                title="Siswa Terdaftar"
                to="/siswaTerdaftar"
                fontWeight="bold"
                icon={<InventoryRoundedIcon />}
                selected={selected}
                setSelected={setSelected}
              />
            </Box>
          </Menu>
        </SidebarContent>
        <SidebarFooter>
        <Box
            sx={{ mt: 2, mb: 2, ml:1 }}
            paddingLeft={isCollapsed ? undefined : "16%"}
          >
            <BasicDialogs
              title="Formulir Daftar Ulang"
              setOpen={setOpen}
              open={open}
            >
              <DaftarUlangForm />
            </BasicDialogs>
            <Button
              onClick={() => setOpen(true)}
              sx={{
                backgroundColor: colors.blueAccent[700],
                color: colors.grey[100],
                fontSize: "14px",
                fontWeight: "bold",
                padding: "10px 20px",
              }}
            >
              <HowToRegRoundedIcon sx={{ mr: "3px" }} />
              {!isCollapsed && "Daftar Ulang!"}
            </Button>
          </Box>
          <Copyright sx={{ mt: 2, mb: 2 }} />
        </SidebarFooter>
      </ProSidebar>
    </Box>
  );
};

export default Sidebar;

问题原因分析

  1. 路由路径大小写不匹配:App组件中路由路径为/siswaterdaftar,但Sidebar中Item的to属性是/siswaTerdaftar,大小写差异会导致第一次点击时路由无法匹配,看起来未跳转。
  2. Link与MenuItem事件冲突:Item组件中把空的Link放在MenuItem内部,同时MenuItem绑定了onClick仅更新选中状态,第一次点击只会修改selected状态,不会触发路由跳转;第二次点击才会触发Link的跳转逻辑,导致需要双击。

解决方案

方案一:统一路径并同步点击事件

  1. 修复路由路径:将App组件中的路由路径改为和Sidebar一致的/siswaTerdaftar:
<Route path="/siswaTerdaftar" element={<SiswaTerdaftar />} />
  1. 修改Item组件:在MenuItem的点击事件中同时触发路由跳转,移除空的Link标签:
import { useNavigate } from "react-router-dom";

const Item = ({ title, to, icon, selected, setSelected }) => {
  const theme = useTheme();
  const colors = tokens(theme.palette.mode);
  const navigate = useNavigate();

  const handleClick = () => {
    setSelected(title);
    navigate(to);
  };

  return (
    <MenuItem
      active={selected === title}
      style={{ color: colors.grey[200] }}
      onClick={handleClick}
      icon={icon}
    >
      <Typography fontWeight="medium">{title}</Typography>
    </MenuItem>
  );
};

方案二:基于路由自动同步选中状态(更推荐)

这种方式让选中状态完全由当前路由决定,避免手动维护状态的不一致:

  1. 修改Sidebar组件:使用useLocation和useMemo根据当前路由自动计算选中项:
import { useLocation, useMemo } from "react-router-dom";

const Sidebar = () => {
  const theme = useTheme();
  const colors = tokens(theme.palette.mode);
  const [isCollapsed, setIsCollapsed] = useState(false);
  const [open, setOpen] = React.useState(false);
  const location = useLocation();

  // 根据当前路由确定选中的菜单项
  const selected = useMemo(() => {
    switch (location.pathname) {
      case "/":
        return "Beranda";
      case "/siswaTerdaftar":
        return "Siswa Terdaftar";
      default:
        return "Beranda";
    }
  }, [location.pathname]);

  // 其余代码保持不变
};
  1. 更新Item组件:移除setSelected参数,让Link包裹菜单项内容,由Link处理路由跳转:
const Item = ({ title, to, icon, selected }) => {
  const theme = useTheme();
  const colors = tokens(theme.palette.mode);

  return (
    <MenuItem
      active={selected === title}
      style={{ color: colors.grey[200] }}
      icon={icon}
    >
      <Link to={to}>
        <Typography fontWeight="medium">{title}</Typography>
      </Link>
    </MenuItem>
  );
};
  1. 同步修改Sidebar中Item的调用:去掉setSelected属性:
<Item
  title="Beranda"
  to="/"
  fontWeight="bold"
  icon={<HomeRoundedIcon />}
  selected={selected}
/>
<Item
  title="Siswa Terdaftar"
  to="/siswaTerdaftar"
  fontWeight="bold"
  icon={<InventoryRoundedIcon />}
  selected={selected}
/>

这样修改后,点击菜单项时Link会直接触发路由跳转,同时选中状态会根据当前路由自动更新,彻底解决双击问题。

内容的提问来源于stack exchange,提问作者Syafiqu Rohman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 14:07:07