qweezz
qweezz

Reputation: 804

How to make a dynamic deeply nested routes with React Router 6?

I'm stuck with dynamic nested routes. Please help to solve this problem.

I'm making a pet-project e-commerce store with showcase and admin panel where admin can add/edit/remove products, categories, brands. Everyting related to admin panel should start with a route domain.com/admin/ and all other routes (without /admin/) should be related to showcase.

My layout for showcase is:

My routes:

  const routes = useRoutes([
    {
      path: PATHS.showcase,
      element: <ShowcasePage />,
      children: [
        {
          path: '/',
          element: <DiscountProductsPage />,
        },
        {
          path: ':url',
          children: [
            {
              index: true,
              element: <CategoryPage />,
            },
            {
              path: ':id',
              element: <ProductsPage />,
            },
          ],
        },
        { path: PATHS.wishlist, element: <WishlistPage /> },
      ],
    },
    {
      path: PATHS.admin,
      element: <AdminPage />,
      children: [
        { path: PATHS.products, element: <ProductsPage /> },
        {
          path: PATHS.settings,
          element: <SettingsPage />,
        },
      ],
    },
  ]);

Path object:

export const PATHS = {
    showcase: '/',
    settings: 'settings',
    products: 'products',
    admin: '/admin/',
    wishlist: '/wishlist'
}

On page load I fetch categories which is an array like this:

const categories = [
  {
    id: '-N8A28QULvmu36EqRZDW',
    description: '',
    name: 'Hoodies',
    url: 'hoodies',
  },
  {
    id: '-z34A29MMMvmu36EqRZXX',
    description: '',
    name: 'Jeans',
    url: 'jeans',
  },
];

Then based on this array I create a menu list passing category as Link state:

<nav>
  <ul>
    {categories.map((category) => (
      <li key={category.id}>
        <Link to={category.url} state={category} onClick={closeMenu}>
          {category.name}
        </Link>
      </li>
    ))}
  </ul>
</nav>

In category page I use state={category} for rendering data like this.

const CategoryPage: React.FC = () => {
  const location = useLocation();
  const { id, name } = location.state as Category;
  const { products } = useSelector((state: RootState) => state.product);
  const categoryProducts = products.filter((product) => product.category.id === id);

  return (
    <section>
      <h1>{name}</h1>
        <ProductCardList products={categoryProducts} />
    </section>
  );
};

Now I want to add a one more dynamic nested route. If user clicks on a product card in category page, then he should land on this url:

domain.com/:categoryUrl/:productId
domain.com/jeans/-s1B99QULvmu36EqCCPP

I added children :id to CategoryPage routes and with that I get an error in CategoryPage component:

Uncaught TypeError: Cannot destructure property 'id' of 'location.state' as it is null.

I understand what causes the error. I don't understand how to fix that. Might be my approach for rendering CategoryPage is wrong? And I need to fix that first and then everything will work?

To clarify what I want:

Upvotes: 1

Views: 2250

Answers (1)

Drew Reese
Drew Reese

Reputation: 202686

Issue

You are rendering CategoryPage as a layout route but it isn't rendering an Outlet component for nested routes to match and render their content into. Since you describe wanting to render these routes/components all separately anyway then the Outlet isn't want you want.

Solution

Refactor your route config to render CategoryPage on an index route so CategoryPage and ProductPage are not a parent-child route relationship, per se. "/:url" will render an Outlet component by default and CategoryPage will be rendered as the index route when the path is exactly "/:url", and ProductPage will render when the path is "/:url/:id".

const routes = useRoutes([
  {
    path: PATHS.showcase,
    element: <ShowcasePage />,
    children: [
      {
        path: '/',
        element: <DiscountProductsPage />,
      },
      {
        path: ':url',
        children: [
          {
            index: true,
            element: <CategoryPage />,
          },
          {
            path: ':id',
            element: <ProductPage />,
          },
        ],
      },
      { path: PATHS.wishlist, element: <WishlistPage /> },
    ],
  },
]);

Upvotes: 1

Related Questions