DEV Community

Joe Christensen
Joe Christensen

Posted on

Nested Routing in react-router-dom

Alt Text

What is react-router-dom?

Ever wondered how websites have custom paths? How come you can type in https://dev.to/readinglist and your browser knows to bring you directly to your dev.to reading list? Well, the website you're navigating to, in our case dev.to, has set up some custom routes. They either have client-side or server-side routes. Client-side routes are generally used in single-page applications. They allow for routing capabilities without needing multiple html pages. Server-side routes are used in multi-page applications, where each route returns a new html page with content.

If you're using React to build your webpage and you want to accomplish something like that, then you'll want to turn your attention to react-router-dom! React-router is a node module that makes use of the React component structure to add client-side routing functionality to a webpage.

Going forward on this blog, I'm going to assume that you have a basic understanding of Switches and Routes in react-router. If you'd like to read up on them before continuing, you can find the documentation here

What is nested routing?

Nested routing is the act of "burying" routes inside each other. Continuing our dev.to example from before, here is an example of a nested route:

https://dev.to/readinglist/archive

First, you route to /readinglist then you route to /archive while inside /readinglist, providing more specific information while still being inside that scope.

Nesting routes helps to create more organized, and clear routes for both the developers and clients to make use of. It also helps practice some separation of concerns. You could have a /readinglist that brings you to your reading list and a /archive that brings you to your reading list archive, but what happens when you want to implement a post archive? The route /archive is already taken, so you'd have to name the new route /postarchive or something, making the routes messy and hard to follow. By nesting them, you can have /readinglist/archive and /post/archive together.

Nested routing in react-router-dom

So, now we know that we know the advantages of nesting routes, how exactly do we do it in react-router-dom?

Lets set up a basic dev.to route layout first:

//App.js

import { 
  BrowserRouter as Router,
  Switch,
  Route
} from 'react-router-dom'

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/posts">
          <p>Posts</p>
        </Route>
        <Route path="/readinglist">
          <p>Reading List</p>
        </Route>
        <Route exact path="/">
          <p>Homepage</p>
        </Route>
      </Switch>
    </Router>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This route layout has three paths currently:

Path Web Display
/posts Posts
/readinglist Reading List
/ Homepage

We can set up nested routing a few ways.

We could add /readinglist/archive by adding a route directly like this:

//App.js
//Inside the Switch

<Route path="/readinglist/archive">
  <p>Reading List Archive<p>
</Route>
Enter fullscreen mode Exit fullscreen mode

Although this would work, what do we do if we want to have a lot of routes nested inside /readinglist? We'd have to add each one to our App.js, quickly making it cluttered and hard to follow. This is why I suggest creating a custom component to store routing logic instead.

Routing logic component

Lets start by creating a custom component to store the routing logic for /readinglist:

//ReadingListRoutes.js

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

function ReadingListRoutes() {

    return (
        <Switch>
            <Route exact path="/readinglist/archive">
                <p>Reading List Archive</p>
            </Route>
            <Route path="/readinglist">
                <p>Reading List</p>
            </Route>
        </Switch>
    )
}

export default ReadingListRoutes;
Enter fullscreen mode Exit fullscreen mode

We've now created our /readinglist route logic component, but we still have to add it to our actual routes! Lets refactor our App.js and do just that:

//App.js

import { 
  BrowserRouter as Router,
  Switch,
  Route
} from 'react-router-dom'

import ReadingListRoutes from './ReadingListRoutes';

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/posts">
          <p>Posts</p>
        </Route>
        <Route path="/readinglist">
          <ReadingListRoutes />
        </Route>
        <Route exact path="/">
          <p>Homepage</p>
        </Route>
      </Switch>
    </Router>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

As you can see, we now have it set up to where the path /readinglist/archive first navigates to our ReadingListRoutes component. Once in our ReadingListRoutes component, it looks for the route /readinglist/archive. If it finds it, it displays Reading List Archive on the webpage.

Our routes table now looks like this:

Path Web Display
/posts Posts
/readinglist Reading List
/readinglist/archive Reading List Archive
/ Homepage

Dynamic Routes

So now we've learned how to do nested routing, but how exactly can we define dynamic routes? Lets take a look at the dev.to url to one of my blogs:

https://dev.to/christensenjoe/classes-in-javascript-f9g

This url first routes to my username, then my blog title (separated by hyphens instead of spaces). But how exactly is dev.to doing this? It cant be hard coding every single possible username as a route.

Well, react-router-dom also provides a nice way to dynamically create routes.

Lets add a basic route to /(username) that will take someone to their custom homepage. I'll go ahead and create a UserRoutes logic component to handle some nested route functionality too:

//App.js

import { 
  BrowserRouter as Router,
  Switch,
  Route
} from 'react-router-dom'

import ReadingListRoutes from './ReadingListRoutes';
import UserRoutes from './UserRoutes';

function App() {
  return (
    <Router>
      <Switch>
        <Route path="/posts">
          <p>Posts</p>
        </Route>
        <Route path="/readinglist">
          <ReadingListRoutes />
        </Route>
        <Route path="/:username">
          <UserRoutes />
        </Route>
        <Route exact path="/">
          <p>Homepage</p>
        </Route>
      </Switch>
    </Router>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
//UserRoutes.js

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

function UserRoutes() {

    return (
        <Switch>
            <Route exact path="/:username/:post_title">
                <p>User Post</p>
            </Route>
            <Route path="/:username">
                <p>User Homepage</p>
            </Route>
        </Switch>
    )
}

export default UserRoutes;
Enter fullscreen mode Exit fullscreen mode

By adding the /:username and /:username/:post_title routes, we can now dynamically create new routes. If you navigate to /joe, you'll be met with the text User Homepage, and if you navigate to /joe/new-post, you'll find text User Post.

Our routes now look like:

Path Web Display
/:username User Homepage
/:username/:post_title User Post
/posts Posts
/readinglist Reading List
/readinglist/archive Reading List Archive
/ Homepage

useParams

We've now dynamically created new routes, but how can we take advantage of them to create custom pages?

First, lets start by creating two new components, UserProfilePage and UserPostPage, and connect them up to our UserRoutes component:

//UserProfilePage.js

function UserProfilePage() {
    return (
        <h1>This is ___'s profile</h1>
    )
}

export default UserProfilePage;
Enter fullscreen mode Exit fullscreen mode
//UserPostPage.js

function UserPostPage() {
    return (
        <h1>This is ___'s post: ___</h1>
    )
}

export default UserPostPage;
Enter fullscreen mode Exit fullscreen mode
//UserRoutes.js

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

import UserPostPage from './UserPostPage';
import UserProfilePage from './UserProfilePage';

function UserRoutes() {

    return (
        <Switch>
            <Route exact path="/:username/:post_title">
                <UserPostPage />
            </Route>
            <Route path="/:username">
                <UserProfilePage />
            </Route>
        </Switch>
    )
}

export default UserRoutes;
Enter fullscreen mode Exit fullscreen mode

Now that we have separate pages, we can make use of a custom react-router-dom hook called useParams(). useParams allows us to grab any params from the current route at the time of invocation. For example, if we went to /:username/:post-title, we could grab the params username and post-title. These variables would store whatever we are currently routed to. So if we're routed to /joe/new-post, then username stores the string joe and post-title stores the string new-post.

Let's update our UserProfilePage and UserPostPage to make use of the useParams() hook to add custom messages:

//UserProfilePage.js

import {
    useParams
} from 'react-router-dom';

function UserProfilePage() {
    const { username } = useParams();
    return (
        <h1>{`This is ${username}'s profile`}</h1>
    )
}

export default UserProfilePage;
Enter fullscreen mode Exit fullscreen mode
//UserPostPage.js

import {
    useParams
} from 'react-router-dom';

function UserPostPage() {
    const { username, post_title } = useParams();
    const parsedTitle = post_title.split("-").join(" ")
    return (
        <h1>{`This is ${username}'s post: ${parsedTitle}`}</h1>
    )
}

export default UserPostPage;
Enter fullscreen mode Exit fullscreen mode

As you can see, we're using useParams to get the username and post_title. We then use them to show some custom messages to our user based on what the put in. On the UserPostPage, we're also mutating our post_title to remove the dashes and replace them with spaces.

Now, if someone navigates to /joe/my-new-post they'll be met with the message This is joe's post: my new post.

Conclusion

React-router-dom is a great node package for React that allows for easy client-side routing. If you're creating a single-page web application, then definitely consider using it to implement static, nested, and dynamic routes. If you'd like to learn more, take a look at the docs here

Top comments (0)