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

Storybook中react-router自定义侧边栏渲染Route报错问题

React自定义侧边栏Storybook路由报错问题

问题背景

我是React初学者,近期尝试开发自定义侧边栏组件,参考教程完成代码实现,所用依赖版本:

  • react-router-dom: ^6.3.0
  • storybook: ^6.1.17

相关代码实现

const SidebarNav = styled.nav`
  background: #15171c;
  width: 250px;
  height: 100vh;
  display: flex;
  justify-content: center;
  position: fixed;
  top: 0;
  right: ${({ sidebar }) => (sidebar ? "0" : "-100%")};
  transition: 350ms;
  z-index: 10;
`;

const SidebarWrap = styled.div`
  width: 100%;
`;


const Sidebar = () => {
    const [sidebar, setSidebar] = useState(true);

    
    return (
        <>
          <IconContext.Provider value={{ color: "#fff" }}>
            <SidebarNav sidebar={sidebar}>
              <SidebarWrap>
                {sideBarData.map((item, index) => {
                  return <SubMenu item={item} key={index} />;
                })}
              </SidebarWrap>
            </SidebarNav>
          </IconContext.Provider>
        </>
      );
};

export default Sidebar;

sidebardata.jsx

export const sideBarData = [ {
    title: "Question 4", 
    path: "/test"
    }, 
    // {
    // title: "Question 5", 
    // path: "/testing"
    // }
];

sidebarsubmenu.jsx

import React, { useState } from "react";
import { Link } from "react-router-dom";
import styled from "styled-components";

const SidebarLink = styled(Link)`
display: flex;
color: #e1e9fc;
justify-content: space-between;
align-items: center;
padding: 20px;
list-style: none;
height: 60px;
text-decoration: none;
font-size: 18px;

&:hover {
    background: #252831;
    border-left: 4px solid green;
    cursor: pointer;
}
`;

const SidebarLabel = styled.span`
margin-left: 16px;
`;

const DropdownLink = styled(Link)`
background: #252831;
height: 60px;
padding-left: 3rem;
display: flex;
align-items: center;
text-decoration: none;
color: #f5f5f5;
font-size: 18px;

&:hover {
    background: green;
    cursor: pointer;
}
`;

const SubMenu = ({ item }) => {
const [subnav, setSubnav] = useState(false);

const showSubnav = () => setSubnav(!subnav);

return (
    <>
    <SidebarLink to={item.path}
    onClick={item.subNav && showSubnav}>
        <div>
        {item.icon}
        <SidebarLabel>{item.title}</SidebarLabel>
        </div>
        <div>
        {item.subNav && subnav
            ? item.iconOpened
            : item.subNav
            ? item.iconClosed
            : null}
        </div>
    </SidebarLink>
    {subnav &&
        item.subNav.map((item, index) => {
        return (
            <DropdownLink to={item.path} key={index}>
            {item.icon}
            <SidebarLabel>{item.title}</SidebarLabel>
            </DropdownLink>
        );
        })}
    </>
);
};

export default SubMenu;

BookSideBarLayout.jsx

import React from 'react';
import Sidebar from './BookSideBar';

function Layout(props) {
    return (
        <div>
            <div style={{display: "flex"}}>
            <Sidebar/>
            </div>
        </div>
    );
}

export default Layout;

BookSidebarRoutes.jsx

import React from "react";
import {BrowserRouter, Route,Routes} from "react-router-dom";

import Support  from "./Support";
import Layout from "./BookSideBarLayout";

function MyRoutes() {
    return (
      <BrowserRouter>
        <Layout/>
        <Routes>
        {/* <Route path="/" element={<Sidebar/>}/> */}
        <Route path="/test" element={<Support/>}/>
        </Routes>
        </BrowserRouter>
    );
}

export default MyRoutes;

Storybook故事文件

import { storiesOf } from '@storybook/react';
import StoryRouter from 'storybook-react-router';
import MyRoutes from '../../src/components/BookSidebarRoutes';

// export default {
//     title: 'Side Bar',
//     //decorators : [(Story) => (<MemoryRouter><Story/></MemoryRouter>)]
  
// };

storiesOf('Params', module)
  .addDecorator(StoryRouter())
  .add('params', () => (
    <MyRoutes/>
  ));

export const Default = () => <MyRoutes />;

报错信息

运行故事时持续抛出错误:
A <Route> is only ever to be used as the child of <Routes> element, never rendered directly. Please wrap your <Route> in a <Routes>.
尝试在MyRoutes组件中使用Switch组件调整,问题未解决。


问题原因

  1. 所用的storybook-react-router库默认适配react-router-dom v5版本,和当前使用的v6版本路由上下文不兼容,无法正确识别v6的<Routes>组件逻辑,导致路由校验失败。
  2. 代码存在双重Router嵌套问题:MyRoutes组件内部写了<BrowserRouter>,StoryRouter装饰器又额外包裹了一层路由容器,路由上下文混乱触发报错。
  3. react-router-dom v6已完全移除Switch组件,v6下所有<Route>必须作为<Routes>的直接子元素,使用Switch无效。

修复步骤

  1. 移除不兼容v6的storybook-react-router依赖,不需要额外安装路由相关storybook插件,直接使用react-router-dom v6自带的MemoryRouter作为故事装饰器即可。
  2. 把MyRoutes组件内部硬编码的<BrowserRouter>移到外层,避免嵌套多个路由容器,路由容器根据使用场景区分:生产环境用BrowserRouter,Storybook/测试环境用MemoryRouter。

修正后代码

BookSidebarRoutes.jsx

import React from "react";
import { Route,Routes } from "react-router-dom";
import Support  from "./Support";
import Layout from "./BookSideBarLayout";

function MyRoutes() {
    return (
      <>
        <Layout/>
        <Routes>
          <Route path="/test" element={<Support/>}/>
        </Routes>
      </>
    );
}

export default MyRoutes;

Storybook故事文件

import { MemoryRouter } from 'react-router-dom';
import MyRoutes from '../../src/components/BookSidebarRoutes';

export default {
    title: 'Side Bar',
    decorators : [
      (Story) => (
        <MemoryRouter initialEntries={['/test']}>
          <Story/>
        </MemoryRouter>
      )
    ]
};

export const Default = () => <MyRoutes />;

生产环境根组件使用示例(App.jsx)

import { BrowserRouter } from 'react-router-dom';
import MyRoutes from './components/BookSidebarRoutes';

function App() {
  return (
    <BrowserRouter>
      <MyRoutes />
    </BrowserRouter>
  )
}

export default App;

内容的提问来源于stack exchange,提问作者rohan arora

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 22:57:32