Transitioning from React Router v5 to v6

Transitioning from React Router v5 to v6

React-router is the standard library for routing in react js. It allows users of a react app to move between different sections (components) of the app.

The react-router team announced the release of a stable version of react-router version 6 (v6) towards the end of 2021, but switching from react-router version 5 (v5) to v6 may be difficult due to some big breaking API changes. In this article, we will walk through what is new in v6 and how to upgrade an existing React project from v5 to v6.

To upgrade the version of the react-router package in our app, we navigate to the project folder and run

npm install react-router-dom@[VERSION_NUMBER]

Replace VERSION_NUMBER with the version we want to install, or with “latest” if we want the latest version, like so:

npm install react-router-dom@6

OR

npm install react-router-dom@latest

Note that we have to be connected to the internet to be able to complete the installation else the installation will fail. Also, ensure that the version of react in your project is v16.8 or greater because react-router v6 relies heavily on hooks that were first supported by react v16.8

Switch is replaced with Routes

The first casualty of the v5 era is the Switch component. The Switch component is used to wrap our routes and it ensures that just one matching route is loaded per time. But this does not exist in v6 anymore. We now use the Routes component to do the same thing that Switch does. Note that we still import BrowserRouter to wrap our app in, just as is done in v5.

In v5, we did it as so:

import { BrowserRouter, Switch } from "react-router-dom";

function App() {
    return (
        <BrowserRouter>
            <div className="App">
                <Switch>
                    {" "}
                    {/* Individual Routes come in here */}
                </Switch>
            </div>
        </BrowserRouter>
    );
}
export default App;

But in v6, this is how we will do it

import { BrowserRouter, Routes } from "react-router-dom";

function App() {
    return (
        <BrowserRouter>
            <div className="App">
                <Routes>
                    {" "}
                    {/* Switch changes to Routes */}
                    {/* Individual Routes come in here */}
                </Routes>
            </div>
        </BrowserRouter>
    );
}

export default App;

Update in the definition of Route component

Although the Route component still maintains a spot in v6, the way we will define it is different from the way we did in v5. We will no longer place the component we want to render in any of the ways we did it in v5, we will instead pass it as the value of the element prop.

No more exact prop

In v5, without adding exact as a prop to a Route component, the path will match if a URL starts with the path keyword, and because the matching process is in order from top to bottom. But in v6, we would not be needing the exact prop because the path pattern matching algorithm was changed, and is even more enhanced now.

In v5, we did it as so:

<Switch>
   {/* There were 3 ways we declared routes in v5 */}
   <Route path="/signup" component={Product} />
   {/* OR */}
   {/* This method allows us pass props to the rendered component */}
   <Route path="/games">
       <Product id={2} />
   </Route>
   {/* OR by passing a callback to the render prop */}
   <Route path="/games" render={(props) => <Product {...props} />} />
</Switch>;

In v6,

<Routes>
   {" "}
   <Route path="/games" element={<Product />} />
   {/* And with props for the rendered component */}
   <Route path="/movies" element={<Product id={200} category="shirt" />} />
</Routes>;

Link and NavLink components still take their places in v6. The Link component generally works as it did in v5, but with the NavLink component, the activeClassName and the activeStyle prop were removed. In v5, activeClassName prop was used to apply some CSS classes automatically to the link once it became active, and the activeStyle allowed us to add internal styles to a link when it became active.

But in v6, we can now use a function with information about the active state of the link. The function’s parameter is an object with the property isActive. This property is true when the link is active and false when it is not. The value of isActive now allows us to use conditional expressions to indicate an active style or class name(s).

In v5, we did it as so:

import {NavLink} from “react-router-dom”

{/* … */}
<NavLink
   to="/product"
   style={{ color: "#689" }}
   activeStyle={{ color: "#3072c9" }}
   className="nav_link"
   activeClassName="active"
>
   Products
</NavLink>;

But in v6, we will do it as so:


<NavLink
   to="/product"
   style={({ isActive }) => ({ color: isActive ? "#3072c9" : "#689" })}
   className={({ isActive }) => `link${isActive ? " active" : ""}`}
>
   Product
</NavLink>;

Redirect has made way for Navigate

In v5, we used the Redirect component to take one to another page but it is no longer exported from react-router-dom in v6. It has been replaced with the Navigate component. In v5, we did it as so:

<Route path="/faq">
   <Redirect to="/about" />
</Route>;
<Route path="/about" component={About} />;

But in v6, we will do it as so:

<Route path="/games" element={<Navigate to="/about" />} />;
<Route path="/games" element={<About />} />;

It is important to note that if we just added the Navigate component the way we did in the snippet above, it will only push our navigation to this path onto the navigation stack, but if we intend to replace the current page with a new page, we will add the replace prop to the Navigate component as in so:

<Route path="/games" element={<Navigate replace to="/about" />} />;

Nested Routes

Nested routes, as the name implies, are routes placed in another route. They are used to render more specific information in child components. In v6, we place our nested routes as children of our parent route. Then we introduce the Outlet component, which is exported from react-router-dom in the rendered component to specify where we want the nested information to be displayed. The Outlet component is not necessary but it makes the code cleaner. In v5, we did it as so:

import { useRouteMatch } from "react-router-dom";
function App() {
   return (
       <BrowserRouter>
           <Switch>
               <Route exact path="/about" component={About} />
               <Route path="/product" component={Product} />
           </Switch>
       </BrowserRouter>
   );
}

function Product() {
   let match = useRouteMatch();
   return (
       <div>
           <Switch>
               {/* match.path returns the path specified in parent route. In this case it is "/product" */}
               <Route path={`${match.path}`}>
                   <AllProducts />
               </Route>
               {/* And in this case it is /product/:id */}
               <Route path={`${match.path}/:id`}>
                   <ProductDetail />
               </Route>
           </Switch>

       </div>
   );
}

In v6, we do it as so:

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

function App() {
   return (
       <Routes>
           <Route path="/about" element={<About />} />
           <Route path="/product" element={<Product />}>
               {/* Here the paths of the nested routes are relative to the path of the parent route. */}
               {/* This becomes "/product/" */}
               <Route path="/" element={<AllProducts />} />
               {/* And This becomes "/product/:id" */}
               <Route path="/:id" element={<ProductDetail />} />

           </Route>
       </Routes>
   );
}

function Product() {
   return (
       <Container>
           <>
               <div>Product</div>
               {/* Other content of the parent component */}
           </>
           {/* This is where the nested information begins */}
           <Outlet />
       </Container>
   );
}

Programmatic Navigation

Programmatic navigation occurs when a user is redirected as a result of an event that occurs on a route, such as clicking a button, an API request completing, e.tc. In v5, we could use the useHistory hook to do something like:

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

function Product() {
   const history = useHistory();

   const handleClick = () => {
       //This pushes the new route on top of the navigation stack
       history.push("/new-route");

       //This replaces the current route with the new route in the navigation stack
       history.replace("/new-route");
   };

   return (
       <div>
           <button>Click Me to redirect to new route</button>
       </div>
   );
}

But in v6, useHistory hook is replaced with useNavigate hook, and we use it in different ways.

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

function Product() {
   const navigate = useNavigate();

   const handleClick = () => {
       //This pushes the new route on top of the navigation stack
       navigate("/new-route");

       //This replaces the current route with the new route in the navigation stack
       navigate("/new-route", { replace: true });
   };

   return (
       <div>
           <button>Click Me to redirect to new route</button>
       </div>
   );
}

One cool thing is that we can go forward and backward any number of times on the navigation stack. By using a positive number as a parameter to navigate() above, the route moves that number of steps forward. And a negative number does the same thing backward

// Goes forward
navigate(1)
// Goes forward twice
navigate(2)
// Goes backward
navigate(-1)
// Goes backward three times
navigate(-3)

The Prompt Component

The Prompt component in v5 prevents accidentally leaving a page if there are unsaved changes. But react-router team did not include it in v6, and there is no alternative for it. So if you need the feature, you either manually implement it or move back to v5.

In addition to not including Prompt in the current release, useBlocker and usePrompt also do not work. The react-router team although said in the official docs that they are currently working on adding it back to v6 at some point, but not for the first stable release of 6.x.

Summary

Let us highlight the changes we have gone through.

  • Switch component is replaced with Routes component.

  • Changes in how to place the rendered component of Route.

  • No more exact prop in the Route component.

  • activeClassName and activeStyle props have been removed from NavLink.
  • We can access the isActive state of a NavLink component through a function callback.
  • Redirect component has been replaced with Navigate component.
  • A sleeker way to implement nested routes.

In conclusion, if you think you are not ready to make the switch to v6 from v5 or any other version, you can always install a previous version using this.

npm install react-router-dom@[VERSION_NUMBER]

But then, you will be missing out on some goodies that came with v6 including but not limited to:

  • Enhanced path pattern matching algorithm.
  • Bundle size reduced by 60% according to Bundlephobia

I believe we were able to successfully make that switch to react-router v6 and stop using the Switch component (pun very much intended) 😌.

Have a great coding experience 🙌.

Signing out, Steph Crown ✌️