React Router组件渲染错误:CRM系统开发中的路由问题
React Router组件渲染错误解决方案
问题描述
开发自定义CRM系统时触发React Router报错:
BrowserRouter is not a
component. All component children of must be a or <React.Fragment>
相关代码如下:
import {AppBar, Box, CssBaseline, Divider, Drawer, List, ListItem, ListItemButton, ListItemIcon, ListItemText, Toolbar, Typography, ListSubheader, Breadcrumbs, Link, styled} from '@mui/material' import StackedLineChartIcon from '@mui/icons-material/StackedLineChart'; import PeopleOutlineIcon from '@mui/icons-material/PeopleOutline'; import WorkOutlineIcon from '@mui/icons-material/WorkOutline'; import NavigateNextIcon from '@mui/icons-material/NavigateNext'; import HomeIcon from '@mui/icons-material/Home'; import { useState,useMemo, Fragment } from 'react'; import { BrowserRouter as Route, Routes, Router } from "react-router-dom"; import EmployeeDashboard from "../main-module/components/EmployeeDashboard"; import CollapsibleTable from '../main-module/components/ClientsList'; import EmployeeStats from '../main-module/components/EmployeeStats'; import ProfileMenu from '../main-module/components/ProfileMenu'; import EmployeeOptions from '../main-module/components/EmployeeOptions'; const drawerWidth = 240; const StyledListItemBtn = styled(ListItemButton)(({ theme }) => ({ borderRadius: "4px" })); const WorkPage = () => { const [component, setComponent] = useState("call"); const [selectedIdx, setSelectedIdx] = useState(2); const handleItemClick = (idx) => { setSelectedIdx(idx); }; const drawer = ( <Box > <Toolbar sx={{ display: "flex", alignItems: "center", justifyContent: "space-around" }}> <Typography variant='h5'>office.kr</Typography> </Toolbar> <Divider /> <List subheader={ <ListSubheader>CRM</ListSubheader> }> <ListItem> <StyledListItemBtn selected={selectedIdx === 0} onClick={() => {setComponent("stats"); handleItemClick(0)}} > <ListItemIcon> <StackedLineChartIcon color='secondary'/> </ListItemIcon> <ListItemText primary="Моя статистика" /> </StyledListItemBtn> </ListItem> <ListItem> <StyledListItemBtn selected={selectedIdx === 1} onClick={() => {setComponent("clients"); handleItemClick(1)}} > <ListItemIcon> <PeopleOutlineIcon color='secondary'/> </ListItemIcon> <ListItemText primary="Мои клиенты"/> </StyledListItemBtn> </ListItem> <ListItem> <StyledListItemBtn selected={selectedIdx === 2} onClick={() => {setComponent("call"); handleItemClick(2)}} > <ListItemIcon> <WorkOutlineIcon color='secondary'/> </ListItemIcon> <ListItemText primary="Обзвон" /> </StyledListItemBtn> </ListItem> </List> <Divider /> <EmployeeOptions /> </Box> ); const setContent = () => { switch (component) { case 'call': return <Route path="/dashboard" element={<EmployeeDashboard />} />; case 'clients': return <Route path="/clients" element={<CollapsibleTable />} />; case 'stats': return <Route path="/stats" element={<EmployeeStats />} />; case 'error': return <p>Error</p>; default: throw new Error('Unexpected process state'); } } const elements = useMemo(() => { return setContent(); // eslint-disable-next-line }, [component]) const breadcrumbs = [ <Link underline="hover" key="1" href="/" sx={{ display: 'flex', alignItems: 'center' }} > <HomeIcon sx={{ mr: 1 }} fontSize="inherit" /> Главная </Link>, <Link underline="hover" key="2" href="/" > CRM </Link>, <Typography key="3"> Обзвон </Typography>, ]; return ( <Box sx={{ display: 'flex' }}> <CssBaseline /> <AppBar position="fixed" sx={{ width: { sm: `calc(100% - ${drawerWidth}px)` }, ml: { sm: `${drawerWidth}px` }, backgroundColor: "#161b22", borderBottom: "1px solid #30363d !important" }}> <Toolbar> <Typography sx={{flexGrow: 1}} variant="h6" color="textSecondary">TestEmp</Typography> <ProfileMenu sx={{flexGrow: 0}}/> </Toolbar> </AppBar> <Box component="nav" sx={{ width: { sm: drawerWidth }, flexShrink: { sm: 0 } }} > <Drawer variant="temporary"> {drawer} </Drawer> <Drawer variant="permanent" sx={{ display: { xs: 'none', sm: 'block' }, '& .MuiDrawer-paper': { boxSizing: 'border-box', width: drawerWidth }, }} open > {drawer} </Drawer> </Box> <Box component="main" sx={{ flexGrow: 1, p: 2.5, width: { sm: `calc(100% - ${drawerWidth}px)` } }} > <Toolbar/> <Breadcrumbs separator={<NavigateNextIcon fontSize="small" />} sx={{ pb: 1.5, pl: 0.5 }} > {breadcrumbs} </Breadcrumbs> <Routes> {elements} </Routes> </Box> </Box> ); } export default WorkPage;
错误根源
- 导入错误:把
BrowserRouter错误重命名为Route,导致返回的<Route>实际是BrowserRouter组件,违反了<Routes>必须直接包含<Route>或<React.Fragment>的规则。 - 路由逻辑错误:用
useState切换component返回路由的方式不符合React Router v6规范,无法同步浏览器路由状态。
修复步骤
1. 修正React Router导入
替换错误的导入语句:
// 错误写法 import { BrowserRouter as Route, Routes, Router } from "react-router-dom"; // 正确写法 import { Routes, Route, useNavigate, useLocation } from "react-router-dom";
注意:BrowserRouter应该在应用根组件(如App.js)中全局包裹,无需在WorkPage重复导入。
2. 用useNavigate实现路由跳转
删除component状态,侧边栏点击时直接跳转路由并同步选中状态:
const WorkPage = () => { const navigate = useNavigate(); const location = useLocation(); const [selectedIdx, setSelectedIdx] = useState(2); // 点击侧边栏项跳转路由+更新选中索引 const handleItemClick = (idx, path) => { setSelectedIdx(idx); navigate(path); }; // 路由变化时自动更新侧边栏选中状态 useEffect(() => { switch(location.pathname) { case "/stats": setSelectedIdx(0); break; case "/clients": setSelectedIdx(1); break; case "/dashboard": setSelectedIdx(2); break; } }, [location.pathname]);
3. 修正侧边栏点击事件
更新三个侧边栏项的onClick逻辑:
// 统计页面 <StyledListItemBtn selected={selectedIdx === 0} onClick={() => handleItemClick(0, "/stats")} > // 客户页面 <StyledListItemBtn selected={selectedIdx === 1} onClick={() => handleItemClick(1, "/clients")} > // 呼叫页面 <StyledListItemBtn selected={selectedIdx === 2} onClick={() => handleItemClick(2, "/dashboard")} >
4. 重构<Routes>配置
删除setContent和useMemo代码,直接在<Routes>中定义所有路由:
<Routes> <Route path="/dashboard" element={<EmployeeDashboard />} /> <Route path="/clients" element={<CollapsibleTable />} /> <Route path="/stats" element={<EmployeeStats />} /> <Route path="*" element={<p>Error</p>} /> </Routes>
5. 同步面包屑与路由状态
根据当前路由动态生成面包屑文本:
const getBreadcrumbLabel = () => { switch(location.pathname) { case "/dashboard": return "Обзвон"; case "/clients": return "Мои клиенты"; case "/stats": return "Моя статистика"; default: return ""; } }; const breadcrumbs = [ <Link underline="hover" key="1" href="/" sx={{ display: 'flex', alignItems: 'center' }} > <HomeIcon sx={{ mr: 1 }} fontSize="inherit" /> Главная </Link>, <Link underline="hover" key="2" href="/dashboard" > CRM </Link>, <Typography key="3"> {getBreadcrumbLabel()} </Typography>, ];
核心说明
- React Router v6强制要求
<Routes>的直接子元素只能是<Route>或<React.Fragment>,不能动态返回非Route组件。 - 路由跳转必须使用
useNavigate钩子,才能保持浏览器历史记录与页面状态同步。 - 全局只需要一个
BrowserRouter包裹应用,所有路由组件都要在其范围内。
内容的提问来源于stack exchange,提问作者Богдан Шевченко
相关产品推荐
相关产品推荐

