DEV Community

Cover image for react-router v6 demystified (part 1)
Romain Trotard
Romain Trotard

Posted on • Updated on

react-router v6 demystified (part 1)

react-router version 6 is currently in beta. I guess it will be released soon. So, what a great time to explore this library which is one of the most used routing library for React.

In this series of article, we are going to briefly see the changes compared to the previous version, how to use it and then we will write the specifications and code our own implementation the same way react-router is implemented, it will just an idea of how it's really implemented, it won't have all the feature, but after reading all the article you will be able to explore it on your own on the repository.

Let's get it started.

Difference between v5 and v6

New Route API

The Route component has a new API which is a lot simpler.
The props available are:

  • path (default value "/")
  • element which is the element to display. No more render or component
  • caseSensitive instead of sensitive to tell that the path needs to match with the same case. Default value to false.

In version 5:

<Route path="/" component={HomePage} />

// or

<Route path="/">
  <HomePage />
</Route>
Enter fullscreen mode Exit fullscreen mode

In version 6:

<Route path="/" element={<HomePage />} />
Enter fullscreen mode Exit fullscreen mode

You can nest Route together. For example:

<Route path="hobby" element={<HobbyLayout />}>
  <Route
    path="favorite"
    element={<FavoriteHobbyListBody />}
  />
  <Route path=":name" element={<HobbyDetailBody />} />
</Route>

// with

function HobbyLayout() {
  return (
    <>
      <h1>Hobby layout page</h1>
      {
        // Will display the right nested Route
      }
      <Outlet />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Note: As you can see the above example, it's still possible to define path with path params (ex :name).

Welcome to Routes component

The version 6 introduces a new component name Routes which is kindly the equivalent to the Switch component which is no more present.

And it's also possible to nest Routes

In version 5:

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route path="hobby" component={HobbyPage} />
      </Switch>
    </BrowserRouter>
  );
}

function HobbyPage() {
  const match = useRouteMatch();

  return (
    <Switch>
      <Route path={`${match.path}/favorite`}>
        <FavoriteHobbyListBody />
      </Route>
      <Route path={`${match.path}/:name`}>
        <HobbyDetailBody />
      </Route>
    </Switch>
  );
}
Enter fullscreen mode Exit fullscreen mode

In version 6 it becomes:

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="hobby/*" element={<HobbyPage />} />
      </Routes>
    </BrowserRouter>
  );
}

function HobbyPage() {
  return (
    <Routes>
      <Route
        path="favorite"
        element={<FavoriteHobbyListBody />}
      />
      <Route path=":name" element={<HobbyDetailBody />} />
    </Routes>
  );
}
Enter fullscreen mode Exit fullscreen mode

Note: You may have noticed the trailing /* to parent Route which is important to tell "match deeply".

Perfect example to go to the next part about relative path.

Relative Routes and Links

As you can see in the example above, for the routes path you don't have to take care about the match.url anymore. All path are relatives now, unless you specify it's an absolute path by starting your path with a slash, for example:

// If I am in the `Route` element with the path
// `/hobby`


// Will navigate to `/hobby/favorite`
<Link to="favorite">Favorite hobby link</Link>

// Will navigate to `/about`
<Link to="/about">About page link</Link>


// Route for path `/hobby/favorite`
<Route
  path="favorite"
  element={<FavoriteHobbyListBody />}
/>

// Watch out it is also route for `/hobby/favorite`
<Route
  path="/favorite"
  element={<FavoriteHobbyListBody />}
/>
Enter fullscreen mode Exit fullscreen mode

Routes element are sorted by react-router

This is a cool feature, you don't have to care anymore of the sorting of your Route elements inside the Routes.

react-router will do a smart sorting, you don't have to be afraid that the first Route defines "blocks" all next ones.

For example in version 5:

function App() {
  return (
    <BrowserRouter>
      <Switch>
        <Route path="/">
          <HomePage />
        </Route>
        <Route path="/hobby">
          <HobbyPage />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

function HomePage() {
  return <p>Home page</p>;
}

function HobbyPage() {
  return <p>Hobby page</p>;
}
Enter fullscreen mode Exit fullscreen mode

Even on the url /hobby, you will see the content of HomePage.

Not the case anymore in version 6 with the code below:

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/hobby" element={<HobbyPage />} />
      </Routes>
    </BrowserRouter>
  );
}

function HomePage() {
  return <p>Home page</p>;
}

function HobbyPage() {
  return <p>Hobby page</p>;
}
Enter fullscreen mode Exit fullscreen mode

More changes

I only have listed the more important changes, but there are more. If you want read a well written, with all changes listed, you can read the migration guide to v6.


Specifications for our implementation

Before doing some code, let's describe the specifications:

  • a Routes can only return ONE matching Route
  • we want to be able to nest Route together. For example:
<Route path="hobby" element={<HobbyPageLayout />}>
  <Route path="/" element={<HobbyListBody />} />
  <Route path="favorite" element={<FavoriteHobbyBody />} />
</Route>
Enter fullscreen mode Exit fullscreen mode

In this case the matching nested Route will be accessible by a component named Outlet. For example, for the HobbyPageLayout above :

function HobbyPageLayout() {
  return (
    <>
      <p>Hobby page layout</p>
      {
        // Will be basically HobbyListBody,
        // FavoriteHobbyBody or undefined
      }
      <Outlet />
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • we want to be able to define path variable and be able to access it through useParams hook. For example:
<Route path="hobby/:name" element={<HobbyDetailPage />} />

const { name } = useParams();
Enter fullscreen mode Exit fullscreen mode

Note: Of course, we want to be able to combine nested Route with path variable.

  • we want to be able to do nested Routes. But we do not want the API to be hard. In the nested we do not want to repeat the parent pathname (or using match.path). For example:
<Routes>
  <Route path="hobby/*" element={<HobbyPage />} />
</Routes>

function HobbyPage() {
  return (
    <Routes>
      <Route path="/" element={<HobbyListBody />} />
      <Route
        path="favorite"
        element={<FavoriteHobbyBody />}
      />
      <Route path=":name" element={<HobbyDetailPage />} />
    </Routes>
  );
}
Enter fullscreen mode Exit fullscreen mode
  • we do not want to care about slash while doing nested routes

  • we want to have relative path during navigation and be able to do absolute with leading "/" to our path.

  • have hooks to defines routes :D

Note: You have probably noticed the * to tell match all.

Now in the next article we are going to start the implementation, starting small:

  • no nesting Route and Routes
  • no path params
  • no relative path

Playground

Here is a little code sandbox of react-router v6:


Conclusion

We have seen the new react-router v6 API, that I find simpler and clearer. Relative link are really cool, nor more question to ask ourself like "Do I need a leading/trailing slash for my path?", and will remove a lot of boilerplate code with match.url and match.path.

Specifications are ready for our in coming implementations and explorations.

For the next article, you will need to know the location and history API, you can read my article. And be sure, to be comfortable with React context, spoiler alert: there are quite a lot.


Want to see more ? Follow me on Twitter or go to my Website. 🐼

Discussion (0)