React Router programmatic nested routes not working

Viewed 825

First of all, I apologize for the ambigous title. But I can't explain this in a single sentence.

I'm using react-router v6.

In first, I have a routes.tsx file. This is in src/router/routes.tsx.

This file is defined as below,

import React from 'react'
import { RouteObject } from 'react-router-dom'

import MainLayout from '../layouts/MainLayout'

// const MainLayout = React.lazy(() => import('../layouts/MainLayout'));
const Index = React.lazy(() => import('../pages/Index'));

const routes: RouteObject[] = [
  {
    path: '/',
    element: <MainLayout />,
    children: [
      {
        path: '',
        element: <Index />,
      }
    ],
  },
];

export default routes

Next is the index.tsx file.

import React from 'react'
import ReactDOM from 'react-dom'
import './index.scss'
import App from './App'
import reportWebVitals from './reportWebVitals'

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

reportWebVitals();

App.js is,

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

import './App.css';

import routes from './router/routes'

function App() {
  return (
    <div className="App">
      <BrowserRouter>
        <Routes>
          {routes.map((route, index) => {
            return (
              <Route
                path={route.path}
                key={index}
                element={route.element}
              />
            );
          })}
        </Routes>
      </BrowserRouter>
    </div>
  );
}

export default App;

It works perfectly for first route, /, and MainLayout. But I don't know how render child routes. The Problem is in MainLayout.

MainLayout looks like,

const MainLayout = () => {
  return (
    <Layout className="main-layout">
      <PageContainer>
        <Routes>
          <Route ??? />
        </Routes>
      </PageContainer>
    </Layout>
  );
}

export default MainLayout

I want to render child route(in this case, Index.tsx). But I really don't have any idea for this.

I tried next but it reports errors.

<Routes>
  {routes.map((route, index) => {
    return (
      <Route
        path={route.path}
        key={index}
        element={route.element}
        children={route.children}
      />
    );
  })}
</Routes>

I'm from Quasar Vue.js framework and I want mimic the default structure of that. Is it possible to implement? Or any other good suggestions?

Update 1: I updated codes like below,

// routes.tsx
const routes: RouteObject[] = [
  {
    path: '/',
    element: <MainLayout />,
    children: [
      {
        index: true,
        element: <Index />,
      },
      {
        path: 'test',
        element: <Index />,
      },
    ],
  },
];
// App.js
function App() {
  return (
    <div className="App">
      <Button />
      <div className="bg-primary">Hello!</div>
      <BrowserRouter>
        <Routes>
          {routes.map((route, index) => {
            return (
              <Route
                path={route.path}
                key={index}
                element={route.element}
              >
                {route.children.map((child, index) => {
                  console.log(child.element);
                  return (
                    <Route
                      path={child.path}
                      key={index}
                      index={child.index}
                      element={child.element}
                    />
                  );
                })}
              </Route>
            );
          })}
        </Routes>
      </BrowserRouter>
    </div>
  );
}

And finally MainLayout.tsx,

// MainLayout.tsx
interface MainLayoutProps {
  children: React.ReactNode;
}

const MainLayout = (props: MainLayoutProps) => {
  console.log(props.children); // This prints empty array.
  return (
    <Layout className="main-layout">
      <PageContainer>
        {props.children}
      </PageContainer>
    </Layout>
  );
}

MainLayout.defaultProps = {
  children: [],
};

export default MainLayout

When I enter to https://localhost:3000/test, the children is empty which expected render Index element. I don't know why children of MainLayout is [], even I passed Routes with map method. Any ideas?

1 Answers

I found a way. The keyword was Outlet.

// MainLayout.tsx
import { Outlet } from 'react-router-dom'

const MainLayout = () => {
  return (
    <Layout className="main-layout">
      <PageContainer>
        <Outlet />
      </PageContainer>
    </Layout>
  );
}

export default MainLayout

It works!

Bonus:

I'm using lazy loading. For that, Suspense is required.

// App.js
import React, { Suspense } from 'react'
// Other imports ...

function App() {
  return (
    <div className="App">
      <Button />
      <div className="bg-primary">Hello!</div>
      <BrowserRouter>
        <Suspense fallback={<div>Loading</div>} >
          <Routes>
            {routes.map((route, index) => {
              return (
                <Route
                  path={route.path}
                  key={index}
                  element={route.element}
                >
                  {route.children.map((child, index) => {
                    return (
                      <Route
                        path={child.path}
                        key={index}
                        index={child.index}
                        element={child.element}
                      />
                    );
                  })}
                </Route>
              );
            })}
          </Routes>
        </Suspense>
      </BrowserRouter>
    </div>
  );
}

export default App

Now it works perfectly.

Related