DEV Community

Kibet Amos
Kibet Amos

Posted on

Nested Routes- React Router

Kindly check out the previous post on Routes :

https://dev.to/kibetamos/react-router-31oa

We have been working with routers that are small enough to be rendered entirely in a single file. But as an application grows in scope, it can be useful to split up the router and write Routes nearer to where the related UI logic is written.

Let’s return to our tech article website example, and imagine that the engineering team is building out a Categories feature that will organize tech news articles by their category – front end, back end, mobile development, etc. In addition to a Categories component (which will render links to each individual category), the team has created a Category view that will display all the articles for a given category.

Previously, we might have written a router like this:

// In the top level App component
<Router>
  <Route path={'/categories/:categoryName'}>
    <Category />
  </Route>
  <Route path={'/categories'}>
    <Categories />
  </Route>
  {/* Other Routes */}
</Router>
Enter fullscreen mode Exit fullscreen mode

There’s nothing wrong with this way of routing, but as soon as you start to introduce more features into your application, you may find that having all the routes contained in a single router becomes a bit unwieldy. The way around this is to get comfortable with rendering routes from components nested within your app.

For example, consider the Categories component, which iterates through a list of categories and creates Links for each category:

function Categories ({ categories }) {
  return (
    <ul>
      {
        categories.map(category =>
          <li>
            <Link to={`/categories/${category}`}>
              {category}
            </Link>
          </li>
        )
      }
    </ul>
  );
};
Enter fullscreen mode Exit fullscreen mode

Clicking on a link rendered by this component will cause the URL to change, for example, to /categories/html. According to our previously defined Router, the route '/categories/:categoryName' will then match and the Category component will render.

Notice that the code for the Categories component doesn’t indicate which component will be rendered when the user clicks on one of the category links (it’s the Category component). We have to navigate back to the top-level App component file where the Router is defined in order to see that the Category component will be rendered when the URL changes to /categories/html. This separation of cause and effect is not ideal.

Because React Router handles routing dynamically (eg. routes exist when they are rendered), you can render a Route anywhere within your application. In this case, we can relocate the Route that renders an individual Category component to within the Categories component where the links to that route are defined

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

function Categories ({ categories }) {
  return (
    <div>
      <ul>
        {
          categories.map(category => 
            <li>
              <Link to={`/categories/${category}`}>
                {category}
              </Link>
            </li>
          )
        }
      </ul>

      <Route path={'/categories/:categoryName'}>
        <Category />
      </Route>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

As a result, the top-level router can be simplified:

// In the top level App component
<Router>

  {/* The Category route has been removed. */}

  <Route path={'/categories'}>
    <Categories />
  </Route>

  {/* Other Routes */}
</Router>
Enter fullscreen mode Exit fullscreen mode

Rewriting your routes this way makes it very obvious what will happen when the user clicks on a link. It also allows us to clean up our top-level router by removing the route for an individual category. Splitting routes up this way also makes an application more efficient since Routes are not always rendered. Instead, Routes are only rendered when the UI logic requires them to be.

useRouteMatch
Route nesting improves the organization of Link and Route components in our application. As in the Categories component, it is common that nested Link and Route components stem from the same base URL (in this case, the /categories URL).

Instead of writing out the full URL path, it would be much more flexible if we could create relative paths based on the /categories URL. React Router provides a hook, useRouteMatch(), that makes it incredibly easy to do this.

Below, you can see the basic usage in a component called BandPage that gets rendered by the route '/bands/:band/'. Suppose that the user visits the page /bands/queen/. This page should render a list of relative Links based on the various songs by the band Queen. A Route is also created to render a SongPage for any chosen song:

import { useRouteMatch, Link, Route } from 'react-router-dom';
import { SongPage } from '../SongPage.js'

function BandPage ({ songs }) {
  let { path, url } = useRouteMatch();

  // path = '/band/:band'
  // url = '/band/queen' 

  // Render a list of relative Links and a Route to render a SongPage
  return (
    <div>
      <ul>
        {
          songs.map(songName =>
            <li>
              <Link to={`${url}/song/${songName}`}> 
                {category}
              </Link>
            </li>
          )
        }
       </ul>

       <Route path={`${path}/song/:songName`}>
         <SongPage />
       </Route>
     </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Let’s break this down.

  1. useRouteMatch() should be called inside a component and returns an object with a url and a path property. This object is sometimes referred to as the match object:
  2. The path property contains the dynamic path pattern with URL parameters (eg. /bands/:band) and should be used for creating relative path props for Route components (eg. /bands/:band/songs/:songName)
  3. The url property has the values of URL parameters filled in (eg. /bands/queen) and should be used for creating relative to props for Link components (eg. /bands/queen/songs/we_are_the_champions).

Let’s see how we can use these values within the Categories component to create relative routes to the Category component:

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

function Categories ({ categories }) {
  let { path, url } = useRouteMatch();

  // path = '/categories'
  // url = '/categories' 

  // Even though path and url are the same in this case, use path for relative Routes and url for relative Links
  return (
    <div>
      <ul>
        {
          categories.map(category =>
            <li>
              <Link to={`${url}/${category}`}>
                {category}
              </Link>
            </li>
          )
        }
       </ul>

       <Route path={`${path}/:category`}>
        <Category />
       </Route>
     </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Using the relative url and path values to generate the Link and Route components ensures that they accurately route the user to the correct URL regardless of the route that caused the Categories component to render.

Discussion (0)