简体   繁体   中英

React Router v6 and recursive rendering of Route component from data

Following up from my question React router v6 and relative links from page within route , I'm trying to refactor the routes in our app to be more nested.

Trouble is that it doesn't seem possible to render a Route element recursively from data, because react-router insists that Route is directly inside Route and not wrapped in another component, and I cannot see how to render recursively (to arbitrary depth) any other way.

Reproduction on codesandbox .

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

import "./styles.css";

function GenericPage() {
  return <div className="page">Generic page</div>;
}

const nav = {
  slug: "",
  title: "Home",
  children: [
    {
      slug: "foo",
      title: "Foo"
    },
    {
      slug: "bar",
      title: "Bar"
    }
  ]
};

const RecursiveRoute = ({ node }) => {
  return (
    <Route path={node.slug} element={<GenericPage />}>
      {node.children?.map((child) => (
        <RecursiveRoute node={child} />
      ))}
    </Route>
  );
};

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <RecursiveRoute node={nav} />
      </Routes>
    </BrowserRouter>
  );
}

Error from react-router:

[RecursiveRoute] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>

Issue

As the error indicates, you can't render Route components directly, they must be rendered directly by a Routes component, or another Route component in the case of nested routes.

Solution

Refactor RecursiveRoute to render a Routes component with a route for the current node and then map the node's children to routes that render the RecursiveRoute as an element .

Example:

function GenericPage({ title }) {
  return (
    <div className="page">
      {title} page
    </div>
  );
}

const RecursiveRoute = ({ node }) => (
  <Routes>
    <Route
      path={`${node.slug}/*`}
      element={<GenericPage title={node.title} />}
    />
    {node.children?.map((child) => (
      <Route
        key={child.slug}
        element={<RecursiveRoute key={child.slug} node={child} />}
      />
    ))}
  </Routes>
);

Suggestion

I strongly suggest not trying to roll your own custom route configuration and renderer, use the useRoutes hook instead to do all the heavy lifting for you.

Example:

Refactor the navigation config:

const nav = [
  {
    path: "/",
    element: <GenericPage title="Home" />,
    children: [
      {
        path: "foo",
        element: <GenericPage title="Foo" />
      },
      {
        path: "bar",
        element: <GenericPage title="Bar" />
      }
    ]
  }
];

Pass the config to the useRoutes hook and render the result:

const routes = useRoutes(nav);

...

return routes;

Demo

编辑 react-router-v6-and-recursive-rendering-of-route-component-from-data

I had the same puzzle to solve. In general I solve it by passing a function in Routes component. Here is my solution with few code snippets.

  // in Routes.ts 
  interface IRoutes {
    path: string
    component: JSX.Element
    children?: IRoutes[]
  }

  const routes: IRoutes[] = [
    {
      path: 'warehouse'
      component: <WarehousePage />
      children: [
        {
          path: 'products'
          component: <ProductsPage />
        },
        {
          path: 'units'
          component: <UnitsPage />
        },
      ]
    },
  ]
  
  // in AppRouter.tsx
  const renderRoutesRecursive = (routes: IRoutes[]) =>
    routes.map((route, index) =>
      route.children ? (
        renderRoutesRecursive(route.children)
      ) : (
        <Route
          key={index}
          path={route.path}
          element={route.component}
        />
      ),
    )

  const renderRoutes = useMemo(() => renderRoutesRecursive(routes), [routes])

  return (
    <Routes>
      <Route path='/' element={<Layout />}>
        {renderRoutes}
      </Route>
    </Routes>
  )

  // in Layout.tsx
  const Layout = () => {
    return (
      <>
        <Header />
        <Navigation />
        <Main>
          <Outlet />
        </Main>
        <Footer />
      </>
    )
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM