skip to Main Content

I have a App.js file that includes all the routes that I have. I wanted to make use of React-Router data loader.

import React from 'react'
import { Routes, Route, Navigate, RouterProvider, createBrowserRouter, createRoutesFromElements} from 'react-router-dom'
import { ThemeProvider, CssBaseline } from '@mui/material'
import Login from './pages/global/Login'
import NoSidebarLayout from './pages/global/NoSidebarLayout'
import SidebarLayout from './pages/global/SidebarLayout'
import NotFound from './pages/NotFound'
import ComingSoon from './pages/ComingSoon'
import Home from './pages/Home'
import About from './pages/About'
import Contact from './pages/Contact'
import ItemRoutes from './pages/ItemRoutes'
import CountryRoutes from './pages/country/CountryRoutes'
import PortRoutes from './pages/port/PortRoutes';
import ServicesManagerRoutes from './pages/serviceManager/ServicesManagerRoutes';
import ChargeManagerRoutes from  './pages/chargeManager/ChargeManagerRoutes';
import PortChargesRoutes from './pages/portCharges/PortChargesRoutes';
import PortsRoutes from './pages/ports/PortsRoutes';
import PrivateRoutes from './components/PrivateRoutes';
import { ColorModeContext, useAppTheme } from './theme'
import { Provider } from "react-redux";
import store from "./redux/store";
import CountryList, { loader as CountryListLoader } from './pages/country/CountryList';
import Country from './pages/country/Country'

const router = createBrowserRouter(createRoutesFromElements(
  <Route>
    <Route element={<PrivateRoutes />}>
      <Route element={<SidebarLayout />}>
        <Route path="/ui/" element={<Home />} />
        <Route path="/ui/items/*" element={<ItemRoutes />} />
        <Route path="/ui/countries/*" element={<CountryRoutes />} />
        <Route path="/ui/ports/*" element={<PortRoutes />} />
        <Route path="/ui/contact" element={<Contact />} />
        <Route path="/ui/about" element={<About />} />
        <Route path="/ui/service-manager/*" element={<ServicesManagerRoutes />} />
        <Route path="/ui/charge-manager/*" element={<ChargeManagerRoutes />} />
        <Route path="/ui/port-charges/*" element={<PortChargesRoutes />} />
        <Route path="/ui/port-manager/*" element={<PortsRoutes />} />
        <Route path="/ui/coming-soon/*" element={<ComingSoon />} />
        <Route path="*" element={<NotFound />} />
      </Route>
    </Route>
    <Route element={<NoSidebarLayout />}>
      <Route path="/ui/login" element={<Login />} />
      <Route path="*" element={<NotFound />} />
    </Route>
  </Route>
))

const App = () => {
  const [theme, colorMode] = useAppTheme();

  return (
    <Provider store={store}>
      <ColorModeContext.Provider value={colorMode}>
        <ThemeProvider theme={theme}>
          <CssBaseline />
          <div className="app">
            <RouterProvider router={router}/>
          </div>
        </ThemeProvider>
      </ColorModeContext.Provider>
    </Provider>
  )
}

export default App

on the CountryRoutes I have this, which includes the loader

import React from 'react'
// import { Routes, Route } from 'react-router-dom';
import { Routes, Route, Navigate, RouterProvider, createBrowserRouter, createRoutesFromElements } from 'react-router-dom'
import CountryList, { loader as CountryListLoader } from './CountryList';
import Country from './Country';

const CountryRoutes = () => (
  <Routes>
    <Route >
      <Route index element={<CountryList />} loader={CountryListLoader} />
      <Route path=":id" element={<Country />} />
    </Route>
  </Routes>
);

export default CountryRoutes;

With this setup the loader is not working but when I directly add the children routes to the parent like this,

const router = createBrowserRouter(createRoutesFromElements(
  <Route>
    <Route element={<PrivateRoutes />}>
      <Route element={<SidebarLayout />}>
        <Route path="/ui/" element={<Home />} />
        <Route path="/ui/items/*" element={<ItemRoutes />} />
        <Route path="/ui/countries/*">
          {/* Directly adding the children routes  */}
          <Route index element={<CountryList />} loader={CountryListLoader} />
          <Route path=":id" element={<Country />} />
        </Route>
        <Route path="/ui/ports/*" element={<PortRoutes />} />
        <Route path="/ui/contact" element={<Contact />} />
        <Route path="/ui/about" element={<About />} />
        <Route path="/ui/service-manager/*" element={<ServicesManagerRoutes />} />
        <Route path="/ui/charge-manager/*" element={<ChargeManagerRoutes />} />
        <Route path="/ui/port-charges/*" element={<PortChargesRoutes />} />
        <Route path="/ui/port-manager/*" element={<PortsRoutes />} />
        <Route path="/ui/coming-soon/*" element={<ComingSoon />} />
        <Route path="*" element={<NotFound />} />
      </Route>
    </Route>
    <Route element={<NoSidebarLayout />}>
      <Route path="/ui/login" element={<Login />} />
      <Route path="*" element={<NotFound />} />
    </Route>
  </Route>
))

Notice that I directly added the children route to the "/ui/countries/*". I am a bit confused to which router should I add the loader.

2

Answers


  1. This bug is most likely being caused by the use of the <Routes/> component.

    Per the docs, <Routes/> is seldom used in conjunction with a data router such as createBrowserRouter(). Why? Because of this well-hidden tidbit of knowledge, which can be paraphrased thus—

    Unlike in BrowserRouter, if a <Routes/> component is used in the new RouterProvider, its children cannot leverage any of the new Data APIs.

    Okay, so how can you ensure all routes can access the data APIs? You have two options.

    1. You can lift each <Route> defined within a <Routes/> component to the top level, Continue shifting them upward one by one until all routes are defined as direct children of the RouterProvider. In your case this is createBrowserRouter().

    Although unaware, you did this by directly adding the children’s routes to the parent; this also explains why the loader works.

    1. You can use a <Route/> component to create a pathless layout route around the target routes instead of using <Routes/>. However, for this to work, an must be added for the child routes to render, especially the index. Index routes, as defined in the docs, render "in their parent route’s outlet at the parent route’s path."

    In your case, you could edit the <CountryRoutes /> component to look something like this:

    const CountryRoutes = () => (
    
      <>
        <h1>Countries</h1>
        <Outlet />
      </>
    
    )
    

    Then, in the <App /> component, you could nest the index and dynamic country routes.

    <Route element={<SidebarLayout />}>
    
      <Route path='/ui/' element={<Home />} />
      <Route path='/ui/items/*' element={<ItemRoutes />} />
    
      <Route path='/ui/countries/*' element={<CountryRoutes />}>
        <Route index element={<CountryList />} loader={CountryListLoader}/>
        <Route path=':id' element={<Country />} />
      </Route>
    
      <Route path='/ui/ports/*' element={<PortRoutes />} />
      <Route path='/ui/contact' element={<Contact />} />
      <Route path='/ui/about' element={<About />} />
    
      ...
    </Route>
    

    Of course, although these edits should provide a good starting point, you will likely need to tweak them a bit for best results.

    Login or Signup to reply.
  2. React loader doesn’t work with descendent routes.

    I am a bit confused to which router should I add the loader.

    This is the intended and expected behavior.

    Route loaders and actions only work in routes declared in the Data router, they are not accessible in descendent routes, e.g. routes rendered in a nested Routes component of a routed component.

    See Routes component:

    If you’re using a data router like createBrowserRouter it is
    uncommon to use this [Routes] component as routes defined as part of a
    descendant <Routes> tree cannot leverage the Data APIs available to
    RouterProvider apps
    . You can and should use this [Routes] component
    within your RouterProvider application while you are migrating.

    The second snippet works because you have included the countries routes directly in the createBrowserRouter router declaration.

    const router = createBrowserRouter(createRoutesFromElements(
      <Route path="ui">
        <Route element={<PrivateRoutes />}>
          <Route element={<SidebarLayout />}>
            <Route index element={<Home />} />
            <Route path="items/*" element={<ItemRoutes />} />
            <Route path="countries">
              <Route index element={<CountryList />} loader={CountryListLoader} />
              <Route path=":id" element={<Country />} />
            </Route>
            <Route path="ports/*" element={<PortRoutes />} />
            <Route path="contact" element={<Contact />} />
            <Route path="about" element={<About />} />
            <Route path="service-manager/*" element={<ServicesManagerRoutes />} />
            <Route path="charge-manager/*" element={<ChargeManagerRoutes />} />
            <Route path="port-charges/*" element={<PortChargesRoutes />} />
            <Route path="port-manager/*" element={<PortsRoutes />} />
            <Route path="coming-soon/*" element={<ComingSoon />} />
            <Route path="*" element={<NotFound />} />
          </Route>
        </Route>
        <Route element={<NoSidebarLayout />}>
          <Route path="login" element={<Login />} />
          <Route path="*" element={<NotFound />} />
        </Route>
      </Route>
    ));
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search