Search code examples
javascriptreactjsreact-router

Why are empty routes needed for this React Router code to work?


Using React (and React Router) I am creating a navigation menu inside of another navigation menu. I created exactly what I am looking for but I don't understand part of the code and I want some clarity. The code I don't understand is commented with: "why is this needed".

As an experiment, I asked chat gpt to rewrite my code and it did so with the commented code removed, but that version does not work.

The code below works because it lets me select the LINK named Topology and this renders a component named Topology. Inside the rendered component are three additional links , when each of these are clicked a corresponding component is rendered.

I attached a little video of me clicking the links and the routes changing (for some reason the video didn't capture my mouse cursor but the links I click are reflected in the URL change)

Video of render

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



function WorkSpace({children}){
  return(
    <div >
    <div>{children}</div>
    <h1>WORKSPACE</h1>
     </div>

  )
}

function Topology(){
  return(  
  
    <div  >

     <nav >
     <Link to="topology" >Topology</Link>
     <br/>
     <Link to="packet-broker"  >Packet Broker</Link>
     <br/>
     <Link to="slice" >Slice</Link>
    </nav>
    <h1>Topology</h1>

    <Routes>
      <Route path="topology" element={<h1>TOPOLOGY WINDOW</h1>}/> 
      <Route path="slice" element={<h1>SLICE WINDOW</h1>} /> 
      <Route path="packet-broker" element={<h1>Packet Broker WINDOW</h1>} /> 
    </Routes>

 
    </div>
  )
}


function MiniTabsRenderSpace(){

  return (
    <div>
    <Routes>
      <Route path="topology" element={<Topology />}> 
        <Route path="topology" element={<></>} />         // <--Why is this needed?
        <Route path="slice" element={<></>} />            // <--Why is this needed?
        <Route path="packet-broker" element={<></>} />    // <--Why is this needed? 
      </Route>
    </Routes>

    </div>
  );

}



function Nav() {
  return (
    <nav>
      <ul>
      <Link to="/topology">Topology</Link>
      <br/>
      <Link to="/another-link-1">Another Link-1</Link>
      <br/>
      <Link to="/another-link-2">Another Link-2</Link>
      </ul>
    </nav>
  );
}


function App() {
  return (

      <div>
        
          <Nav/>
          <WorkSpace>
          <MiniTabsRenderSpace/>
          </WorkSpace>
        
  
      </div>
  
  );
}

export default App;

Solution

  • You need the nested "/topology" routes because Topology is rendered as a layout route and React-Router needs the nested routes to exist in the routing tree to have a leaf node that is "matchable". It's unclear how exactly React-Router is then able to render the appropriate routed components from the descendent routes Topology is rendering, but I'll assume this is an internal implementation detail that just happens to work out since they all technically render on the same URL path.

    That said, your implementation is incorrect.

    If you wish to keep Topology as-is rendering descendent routes

    • Update your routes to render "/topology" as a layout route, and render Topology as both an index route (i.e. on "/topology") component and on a splat route (i.e. path="*") component to allow descendent route matching, i.e. the children routes it renders.

      function MiniTabsRenderSpace() {
        return (
          <div>
            <Routes>
              <Route path="topology">
                <Route index path="*" element={<Topology />} />
              </Route>
            </Routes>
          </div>
        );
      }
      
    • Update the Link components to correctly navigate relative to the parent "/topology" path by prepending them with "..".

      function Topology() {
        return (
          <div>
            <nav>
              <Link to="../topology">Topology</Link>
              <br />
              <Link to="../packet-broker">Packet Broker</Link>
              <br />
              <Link to="../slice">Slice</Link>
            </nav>
            <h1>Topology</h1>
      
            <Routes>
              <Route path="topology" element={<h1>TOPOLOGY WINDOW</h1>} />
              <Route path="slice" element={<h1>SLICE WINDOW</h1>} />
              <Route path="packet-broker" element={<h1>Packet Broker WINDOW</h1>} />
            </Routes>
          </div>
        );
      }
      

    If you wish to use Topology as a layout route

    • Update your routes to render "/topology" as a layout route that renders Topology. Render the nested routes under this parent layout route.

      function MiniTabsRenderSpace() {
        return (
          <div>
            <Routes>
              <Route path="topology" element={<Topology />}>
                <Route path="topology" element={<h1>TOPOLOGY WINDOW</h1>} />
                <Route path="slice" element={<h1>SLICE WINDOW</h1>} />
                <Route path="packet-broker" element={<h1>Packet Broker WINDOW</h1>} />
              </Route>
            </Routes>
          </div>
        );
      }
      
    • Update the Link components to correctly navigate relative to the parent "/topology" path by prepending them with ".". Note that not using prepending "." to the link target paths will still work, it's suggested to be more explicit. Render an Outlet component in place of the routes so the nested routes have a place to render out their element content to.

      import { Link, Outlet } from 'react-router';
      
      function Topology() {
        return (
          <div>
            <nav>
              <Link to="./topology">Topology</Link>
              <br />
              <Link to="./packet-broker">Packet Broker</Link>
              <br />
              <Link to="./slice">Slice</Link>
            </nav>
            <h1>Topology</h1>
      
            <Outlet />
          </div>
        );
      }
      

    Important Note

    In React-Router 7 you should import everything from react-router unless you have specific platform needs, in which case you would import from react-router/<platform>, e.g. import { RouterProvider } from 'react-router/dom';. react-router re-exports all from react-router-dom, but not the other way around. react-router-dom should be removed as a project dependency, replaced by just react-router.

    1. Uninstall react-router-dom: npm uninstall react-router-dom

    2. Install react-router: npm install react-router

    3. Update imports:

      From

      import { Routes, Route, Link } from 'react-router-dom';
      

      to

      import { Routes, Route, Link } from 'react-router';