Search code examples
reactjsreact-hooksreact-routerreact-router-dom

React Router v6 nested lazy with useRoutes hook issue


Using react-router v6 and react v18. I am not sure what I am missing, but it seems like the nesting component with lazy does not work. I have this file structure:

  • routes/root.tsx
  • routes/profiles/Profiles.tsx
  • routes/profiles/ProfileDetail.tsx
  • routes/profiles/ProfilesList.tsx

Now in the root, I have a router defined like this:

createBrowserRouter([
  {
    path: "/",
    element: <AuthLayout />,
    children: [
      {
        path: "/profiles/*",
        lazy: () => import("./profiles/Profiles.tsx"),
      },
    ],
  },
])

And then in the Prfiles.tsx:

export const Component = () => {
  // some code

  return useRoutes([
    {
      path: "/",
      lazy: () => import("./ProfilesList"),
    },
    {
      path: "/:id",
      lazy: () => import("./ProfileDetail"),
    },
  ])
}

Both ProfilesList.tsx and ProfileDetail.tsx export a Component.

The AuthLayout gets rendered, but when I go to /profiles or /profiles/someId it does not ender anything. It just shows empty page. (with a menu that is contained in the AuthLayout. The Component in Profiles.tsx does get rendered, but the profile list and detail does not.

Am I missing something? I dont see a reason why this should not work.

I tried to not lazy load the Profiles.tsx component in the root router config. If instead of using lazy I use element inside the Profiles.tsx component for the routes. It works as expected, but when I use tha lazy option it does not work.

Since the lazy function just get a function with the file and uses its Component,loader etc... I would suspect that it would work the same way as with other routes like the Profile.tsx itself where exporting a Component and than lazy in the createBrowserRouter does work.


Solution

  • it seems like the nesting component with lazy does not work.

    It is my opinion that the official docs might be a bit misleading when it comes to lazily loading components using the Route component's lazy prop when they include this note:

    This feature only works if using a data router, see Picking a Router

    It seems the caveat to this is that the lazy only works when literally used within the createBrowserRouter declaration. Since the "/profiles" route renders descendent routes it is difficult (or impossible) for the Data router to know what routes will be rendered and available at runtime, so the Profiles component can't use React-Router's lazy loading solution.

    You'll need to do all the routing/loading in where the router is created. The Profiles component should be converted to a layout route component, e.g. it renders an Outlet for the nested routes instead of directly rendering the descendent routes manually.

    Example:

    Profiles.tsx

    import { Outlet } from 'react-router-dom';
    
    export const Component = () => {
      // some code
    
      return (
        ...
        <Outlet />
        ...
      );
    }
    
    const router = createBrowserRouter([
      {
        path: "/",
        element: <AuthLayout />,
        children: [
          {
            path: "/profiles",
            lazy: () => import("./profiles/Profiles.tsx"),
            children: [
              {
                index: true,
                lazy: () => import("./profiles/ProfilesList.tsx"),
              },
              {
                path: ":id",
                lazy: () => import("./profiles/ProfileDetail.tsx"),
              },
            ],
          },
        ],
      },
    ]);
    

    Alternatively you can use the regular React lazy import solution if you stil want to code split and use descendent routes.

    Example:

    Both ProfilesList and ProfileDetail should be default exported

    const Component = () => {
      ...
    };
    
    export default Component;
    

    Profiles.tsx

    import { lazy, Suspense } from "react";
    import { useRoutes } from "react-router-dom";
    
    const ProfilesList = lazy(() => import("./ProfilesList"));
    const ProfileDetail = lazy(() => import("./ProfileDetail"));
    
    export const Component = () => {
      // some code
    
      const profilesRoutes = useRoutes([
        {
          path: "/",
          element: <ProfilesList />,
        },
        {
          path: "/:id",
          element: <ProfileDetail />,
        },
      ]);
    
      return (
        <>
          <h1>Profiles</h1>
          <Suspense fallback={<h1>Loading...</h1>}>{profilesRoutes}</Suspense>
        </>
      );
    };