Migrating to React Router v6: A complete guide

It may be challenging to transition from React Router v5 to v6, because several changes have altered how things were done in React Router v5. In addition, a number of new features have been introduced, so it is recommended to upgrade to v6 even if the transition will be slightly annoying.

To upgrade from React Router v5 to v6, you’ll either need to create a new project or upgrade an existing one using npm. React Router v6 also makes extensive use of React Hooks, requiring React v16.8 or above.

In this article, we’ll look at issues with React Router v5, what changed, how to upgrade to v6, and what benefits this upgrade has to offer. In order to follow along, you should be familiar with React Router.

Issues with React Router v5

React Router v5 came the close to perfection, but there were still some flaws.

First, history.push() does not trigger navigation; it was observed that history.push() updated the browser’s url but does not navigate to the path. Learn more about the issue here.

Next, push and replace retain previous search and hash strings. When the push or replace method is executed on history, the search and hash strings from the previous routes remain in the new route. Learn more about the issue here.

Another issue is that Routes queries route paths with the most similar naming instead of with exact naming. For instance, a route name /test would still be called if the browser route is //t/te/tes, or /test, which is incorrect. Developers have to specify the prop exact to strictly query route names.

Finally, defining routes with the same path but optional parameters needs special arrangements. For instance, when defining the route /games and another route with the same name, the parameter /games/:id is required. Developers need to arrange the definition so the route with the parameter comes first, otherwise the routes don’t work as expected.

// The correct way: Here '/games' will render 'Games' component but not 'SelectedGame', because it does not have a parameter 'id'. While '/games/:id' will render only the 'selectedGame' component
<Router>
  <Route path="/games/:id" component={SelectedGame} />
  <Route path="/games" component={Games} />
</Router>

// The wrong way: Here either '/games' or '/games/:id' will render the 'Games' components
<Router>
  <Route path="/games" component={Games} />
  <Route path="/games/:id" component={SelectedGame} />
</Router>

The code snippet above illustrates the right and wrong way to order routes that are related by paths or parameters. The first example is the most suitable order, while the second example allows only the route /games to render for any situation where /games has a parameter.

Having considered the issues with React Router v5, we will now discuss how to migrate and what has changed that makes React Router v6 different.

Migrating to React Router v6

The following sections will teach you how to upgrade to React Router v6 in projects where React Router is already installed, and from scratch.

Upgrading React Router in a project where it is already installed

To upgrade the React Router version, open a terminal and navigate to the project directory where you wish to upgrade it.

To see a list of the project’s dependencies, use the following command:

npm ls

You should see a list like this:

List indicating which version of React Router is currently installed

Although the list generated may not be exactly the one above, if you have React Router installed, you should see the most recent version.

Next, run the following command to initiate an upgrade:

npm install react-router-dom@latest

If you execute this command without being connected to the internet, it will fail because some files must be downloaded during the installation. If everything is in order, you should see something similar; in our case, the most recent version is v6.0.2:

Update confirmation page from npm

If everything goes well, we should notice that React Router has been updated when we run the npm ls command again.

List showing React Router updated to v6

Installing React Router from scratch

First, open a terminal in a project directory where React Router isn’t installed. To install a specific version of React Router, run:

npm install react-router-dom@[VERSION_TO_BE_INSTALLED]

Replace [VERSION_TO_BE_INSTALLED] with the version you want to install.

Next, run the following code to install the newest version:

npm install react-router-dom

This installation also demands the use of the internet. If the installation went well, you should see something similar to this:

npm confirmation of react router install

What changed?

It’s important to understand what changed so that we can see why upgrading to React Router v6 is helpful. Note that in certain circumstances, developers will downgrade a program to increase functionality or avoid issues.

We will go through the changes in React Router v5 that one should think about when choosing which version to implement in a project.

Setting up routes

We had around three different techniques for generating routes in React Router v5, which caused confusion. They are as follows:

The first technique is to pass the component and path as prop of the Route component:

<Route path="/games" component={Games} />

This works well, however we are unable to pass props to the rendered component.

The second is to pass in the component as a child of the Route component:

<Route path="/games">
<Games count=”10” category=”Action” />
</Route>

We may pass custom props to the component we want to render using this approach.

The third and final technique is to use the render prop where a function returns the component to be rendered:

<Route path="/games" render={(props) => <Games {…props} />} />

This also works and lets us to give props to the component we’re rendering. However, it is ambiguous and prone to inaccuracy.

In React Router v6, routes have been simplified to the point that we no longer need to utilize Switch to query them. Instead, we utilize a “required” component called Routes, which only searches for routes by name. The * character can be used to query using a wildcard.

We then supply the component to be rendered to the Route component as element props. We can also supply custom props to the components in each route that we wish to render.

The code snippets below demonstrate how to define routes in v6:

<Routes>
  <Route path="/games" element={<Games />} />
  <Route path="/movies" element={<Movies genre="Action" age="13" />} />
</Routes> 

You can also use useRoutes to query the routes in the app. To do this, we must alter the index.js content, where we change the App wrapper from React.StrictMode to BrowserRouter as below:

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { BrowserRouter } from 'react-router-dom'
ReactDOM.render(
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById('root')
);

Then, in the App.js file, we define the routes by performing the following:

import { Outlet, useRoutes } from 'react-router-dom';
const App = () => {
  let routes = useRoutes([
    { 
      path: '/', 
      element: <div>Hello Index</div> 
    },
    { 
      path: 'games', 
      element: <Games />,
      children: [
      { 
        path: '', 
        element: <div>Games Index</div>
      },
      { 
        path: ':id', 
        element: <div>Game Details</div>
      }
    ]
  }
]);
  return routes;
}
export default App
const Games = () => {
  return (
    <div className="Games">
      <div>This is the Games pages</div>
      <Outlet />
    </div>
  );
}

Here, we imported Outlet and useRoutesuseRoutes allows us to define the routes as an array of objects in which we can specify a path, the element to be rendered when the path is browsed, and sub-paths. Outlet helps us render the child route that matches the current path.

Redirect to another route

In React Router v5, we use useHistory() to redirect, and as previously mentioned, there have been concerns with this technique.

To implement it, we normally do the following:

import { useHistory } from "react-route-dom";

const App = () => {
  const history = useHistory();
  const handleClick = () => {
    history.push("/home")
  }
  return (
    <div>
      <button onClick={handleClick}>Go Home</button>
    </div>
  ) 
}
export default App

In v6, we use useNavigate like so:

import { useNavigate } from "react-router-dom";

const App = () => {
  const navigate = useNavigate();
  const handleClick = () => {
    navigate("/home");
  }
  return (
    <div>
      <button onClick={handleClick}>Go Home</button>
    </div>
  );
}
export default App

Learn more about redirecting with useNavigate instead of useHistory.

Specifying exact route paths in NavLink

In v5, when enforcing an exact path or route, we use the exact prop. To implement this we do the following:

<NavLink to="/index" exact>Go Home</NavLink>

In v6, we use the end prop to ensure exact routes:

<NavLink to="/index" end>Go Home</NavLink>

Styling an active NavLink

In React Router v5, we use the activeClassName or activeStyle props to style a NavLink that is currently active.

For instance:

<NavLink to="/home" style={{color: 'black'}} activeStyle={{color: 'blue'}} className="nav_link" activeClassName="active" >Go Home</NavLink> 

In v6, we can now use a function with the argument isActive to condition the style or className to be used for an active NavLink.

For instance:

<NavLink to="/home" style={({isActive}) => ({color: isActive ? 'blue' : 'black'})} className={({isActive}) => `nav_link${isActive ? " active" : ""}`} >Go Home</NavLink>

Use useMatch instead of useRouteMatch

In v5, useRouteMatch was used to create relative subroute paths that matched a particular route.

In v6, we use useMatch for this. Using the useMatch hook requires a pattern argument and does not accept patterns as an array.

What was removed?

In React Router v6, some features are no longer supported, either because they are ambiguous or faulty. So far two features were found to have been removed:

First, usePrompt was used to confirm whether a user want to exit a page, when the page is in a state that does not require the user to leave.

Second, useBlocker is similar to usePrompt, and was used to prevent users from leaving a particular page.

These two feature are similar and gave the same issue. In a situation where a user tries to navigate outside a page and navigation is restricted by either usePrompt or useBlocker, the URL path changes even though the navigation is prevented.

Although there were issues raised about useBlocker and usePrompt, the creators are still considering adding them back to v6 after they release a stable version. Check here for more details.

Benefits of React Router v6 over v5

It’s pointless to migrate if one version doesn’t offer any advantages over the other. In this section, we’ll talk about the benefits of upgrading from React Router v5 to v6.

One of the most important considerations for developers is the application’s portability. The size of React Router v6 has been reduced significantly compared to previous versions. The difference between v5 and v6 is around 60 percent.

Here is a comparison from Bundlephobia:

Compiling and deploying apps will be easier and faster as a result of the size reduction.

In addition, the bulk of the modifications in React Router v6 that we covered are more beneficial and have fewer issues.

As previously mentioned, React Router v6 allows routes to accept components as elements when constructing routes, and pass custom props to components. We may also nest routes and use relative links as well. This helps us prevent or reduce the definition of new routes inside various component files, and it also makes it easy to pass global data to components and props.

When redirecting instead of using useHistory (which had issues with updating the URL), the new approach using useNavigate has been design to fix the issue of redundant URL. It allows us to set the new route manually (navigate("/home")) instead of updating stale routes (history.push("/home")), which might be a bit ambiguous.

Finally, navigation link NavLink allows us to condition active links, which makes our code neater and simpler. Instead of passing two separate props for an active and inactive state. By doing this, we can easily use a ternary operator to condition which style will be affected when a link is active or not.

Conclusion

After reading this article, I hope you are able to upgrade your React Router version and restructure your codebases to work seamlessly with React Router v6.

Not only should React Router v5 be upgraded to v6, but Reach Router should be switched to v6 as well.

Regularly upgrade React Router v6 and review the documentation for any changes to stay up-to-date with new releases.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — start monitoring for free.