Editor’s note: This React Router migration guide was last updated on 25 October 2022 to include information about useNavigate
vs. useHistory
, changes to NavLink
, and more information about React Router v6.
When we maintain React apps, we’ll need to upgrade dependency libraries by changing our existing codebases over time. For example, upgrading the React Rounder v5 dependency to v6 requires step-by-step changes in your existing codebase. 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, several new features have been introduced, so it is recommended to upgrade to v6 even if the transition is 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 extensively uses 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 offers. To follow along, you should be familiar with React Router.
Jump ahead:
NavLink
React Router v5 came close to perfection, but there were still some flaws. The v5 library has some issues with the routing algorithm and routing configuration defaults (JSX component prop defaults). Also, the v5 came with some inconsistent, somewhat complex APIs that affect the developer’s productivity. Moreover, v5’s bundle size weighs more compared to what it offers as a React library. Here is a detailed explanation of the major issues that every developer faces in React Router v5:
Routes
queries route paths with the most similar naming instead of with exact naming. For instance, a route name /dashboard
would still be called if the browser route is /dashboard/test
, or /dashboard/testagain
, etc., which is not expected by app developers. Developers have to explicitly specify the prop exact
to strictly query route names.
Also, defining routes with the same path but optional parameters requires special arrangements. For instance, the parameter is required when defining the route /games
and another route with the same name. Thus, developers need to arrange the definition, so the route with the parameter comes first. Otherwise, the routes won’t work as expected because of the issues in v5’s routing algorithm.
Look at the following example that demonstrates the ranking problem in the v5’s routing algorithm:
The correct way: Here, /games
will render the 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 incorrect way: Here, either /games
or /games/:id
will render the Games
component:
<Router> <Route path="/games" component={Games} /> <Route path="/games/:id" component={SelectedGame} /> </Router>
The above code snippets illustrate the right and wrong way to order routes that are related by paths or parameters according to React Router v5. The first example is the most suitable order, while the second allows only the route /games
to render for any situation with a parameter.
According to v5’s perspective, the second approach is incorrect, and developers need to use the exact
prop or re-order (as in the first code snippet) to fix it. But, the v5 route ranking algorithm could be intelligent to identify the most suitable route without explicit ordering or exact
usage.
For developing nested routes, developers had to use write more code with the useRouteMatch
Hook, Route
, and Switch
components. For nested routes, developers had to find the entire route because there is no relative route handling in v5:
// ----
<Switch>
<Route path="/dashboard" component={Dashboard} />
</Switch>;
// ----
function Dashboard() {
const { path } = useRouteMatch();
return (
<div>
<h1>Dashboard</h1>
<Switch>
<Route path={`${path}/charts`} component={Charts} />
</Switch>
</div>
);
}
When a specific library offers inconsistent and somewhat complex APIs, application codebases become less maintainable and more error-prone. So, software library developers always try to offer consistent and simple APIs for app developers to manage their codebases better. But, library developers can’t offer the most optimal API design for app developers with their first release. API improvements typically come with the open-source developer community’s feedback, bug reports, and feature requests.
React Router v5 has some inconsistent APIs and unfamiliar naming. Also, app developers had to write more boilerplate code than they expected to configure and integrate React Router v5 into their apps. For example, for programmatic navigation, v5 offers the useHistory
Hook, but almost all React Router newcomers expect useNavigate
.
Also, the Route
component offers two different props to link components: component
and render
, but it’s possible to offer one element
prop as the Suspense API does. Moreover, for defining your routes with JavaScript objects in v5, you have to use the separate react-router-config
package.
As we already know, when we use heavy dependencies, our applications also become heavy. Similarly, if we use a library that requires writing verbose API calls, our application bundles also contain excessive code, which leads to heavy bundle sizes.
React Router v5 latest stable release (v5.3.4) ‘s bundle size is about four times larger than React! 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.
The following sections will teach you how to upgrade to React Router v6 in projects where React Router is already installed and from scratch.
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 # --- or --- yarn list
You should see a list like this:
Although the list generated may not be exactly the one above, you should see an entry with its installed version if you have React Router installed.
Next, run the following command to initiate an upgrade:
npm install react-router-dom@latest # --- or --- yarn 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 version is v6.0.2:
If everything goes well, we should notice that React Router has been updated when we run the npm ls
or yarn list
command again:
First, open a terminal in a project directory where React Router isn’t installed. To install a specific version of React Router, run the following:
npm install react-router-dom@[VERSION_TO_BE_INSTALLED]
Replace [VERSION_TO_BE_INSTALLED]
with the version you want to install, for example, 6.0.2
.
Or, run the following code to install the newest version:
npm install react-router-dom # --- or --- yarn 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:
It’s important to understand what changed so 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 consider when choosing which version to implement in a project.
We had three different techniques for generating routes in React Router v5, which caused confusion. The first technique is to pass the component
and path
as props of the Route
component:
<Route path="/games" component={Games} />
This works well, however, we cannot 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 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 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 = () => { const 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; } const Games = () => { return ( <div className="Games"> <div>This is the Games page</div> <Outlet /> </div> ); } export default App;
Here, we imported Outlet
and useRoutes
. The useRoutes
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.
useNavigate
vs. useHistory
In React Router v5, we use useHistory()
for handling navigation programmatically. There have been concerns with this technique, such as naming confusion and having two methods for navigation, history.push
and history.replace
.
To implement navigation in v5, we usually 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;
We use the history if we need to replace the current history frame.replace('/home')
method call.
In v6, we use useNavigate
instead of useHistory
:
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
Instead of history.replace
, we can call the navigate method with an options object as follows:
const handleClick = () => { navigate('/home', { replace: true }); }
So, what happened to the go
, goBack
, and goForward
methods in the legacy history API in v5? In v6, we can call the same navigate function with numbers. For example, look at the following code snippet that demonstrates go back
, forward
, and 2nd history
frame navigation:
navigate(-1) // v5's history.go(-1) or history.goBack() navigate(1) // v5's history.go(1) or history.goForward() navigate(2) // v5's history.go(2)
Also, v6 replaces Redirect
with a declarative version of useNavigate
, Navigate
:
import { Navigate } from 'react-router-dom'; function App() { return <Navigate to='/home' replace state={state} />; }
Moreover, the navigate API is now suspense-ready. You can inspect a sample suspense-based router implementation from this source file on GitHub.
NavLink
The v6 versions also changed the NavLink
component interface that helps you create breadcrumbs, tabs, and navigation menus with dynamic styles. This is a frequently used component in most React Router-based apps, so you may have to allocate more time for NavLink
related rewrites.
NavLink
In v5, we use the prop when enforcing an exact path or route. To implement this, we do the following:
<NavLink to="/dashboard" exact>Go Home</NavLink>
In v6, we use the end
prop to ensure exact routes:
<NavLink to="/dashboard" end>Go Home</NavLink>
The React Router team renamed this prop to align the library with the React ecosystem’s practices.
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 have to use a function with the destructured 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}) => `ln-${isActive ? ' active' : ''}`} > Go Home </NavLink>
These changes also fix the consistency and simplicity issues in the v5 legacy API. However, if your React app uses the above-deprecated props extensively, you can make your migration process faster and smoother by creating a temporary wrapper for v6’s NavLink
as follows:
import * as React from 'react'; import { NavLink as BaseNavLink } from 'react-router-dom'; const NavLink = React.forwardRef( ({ activeClassName, activeStyle, ...props }, ref) => { return ( <BaseNavLink ref={ref} {...props} className={({ isActive }) => [ props.className, isActive ? activeClassName : null, ] .filter(Boolean) .join(" ") } style={({ isActive }) => ({ ...props.style, ...(isActive ? activeStyle : null), })} /> ); } );
The above code converts the v6 NavLink
to a v5-like NavLink
and lets you deploy the app with v6 by keeping working v5-styled NavLinks
that you can re-write without a rush.
useMatch
instead of useRouteMatch
In v5, useRouteMatch
was used to create relative sub-route paths that matched a particular route. We can use this Hook with or without the pattern argument as follows:
// Receive the matched path and url of the current <Route/> const { path, url } = useRouteMatch(); // Receive the matched route details based on the pattern argument const match = useRouteMatch('/users/:id');
In v6, we use useMatch
for this. Using the useMatch
Hook requires a pattern argument and does not accept patterns as an array. So, the following code snippet is valid for v6:
const match = useRouteMatch('/users/:id');
However, v6 throws an error
if you don’t pass the pattern argument to the Hook. The above match
constant will receive details about the route match.
In v5, creating JSX-based nested routes generated more boilerplate code segments since we had to construct full URLs explicitly. Consider the following example code snippet:
// ---- <Switch> <Route path="/dashboard" component={Dashboard} /> </Switch>; // ---- function Dashboard() { const { path } = useRouteMatch(); return ( <div> <h1>Dashboard</h1> <Switch> <Route path={`${path}`} component={Summary} /> <Route path={`${path}/stats`} component={Stats} /> <Route path={`${path}/charts`} component={Charts} /> </Switch> </div> ); }
Here we need to manually construct the full path for each’s path
prop using the useRouteMatch
Hook. So, when you develop complex apps, you can see this boilerplate code everywhere.
React Router v6 made JSX-based nested routes definitions so minimal. Look at the following v6 re-write of the above v5 sample code snippet:
// --- <Routes> <Route path="/dashboard" element={<Dashboard/>}> <Route path="" element={<Summary/>}/> <Route path="stats" element={<Stats/>}/> <Route path="charts" element={<Charts/>}/> </Route> </Routes> // --- function Dashboard() { return ( <div> <h1>Dashboard</h1> <Outlet/> </div> ); }
Here v6 lets developers use the Route
component in a nested way to define nested routes. Note the Outlet
component that renders nested components: Summary
, Stats
, and Charts
. Here we don’t need to prepend /dashboard/
to all nested routes, unlike v5, since v6 supports relational path definitions.
In React Router v6, some features are no longer supported because they are ambiguous or faulty. So far, two features were found to have been removed:
Prompt
, usePrompt
, and useBlocker
usePrompt
(JSX Prompt
): Was used to confirm whether a user wants to exit a page when the page is in a state that does not require the user to leaveuseBlocker
: Similar to usePrompt
, and was used to prevent users from leaving a particular pageThese two features are similar and have 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. Meanwhile, the developer community created libraries like react-router-prompt
for app developers who need these removed features in React Router v6.
Using one BrowserRouter
component is enough for developing advanced React app navigations. Some app developers who used React Router v5 practiced using nested routers in their apps to isolate complex components. For example, most developers used v5’s MemoryRouter
inside the BrowserRouter
instance to insolate modals and tab groups. Unfortunately, v6 dropped supporting nested routers and motivated app developers to use only one preferred router implementation.
Most app developers who built nested routers now request the deprecated feature back in v6 to upgrade their v5 React Router dependency to v6. Some developers worry about this breaking change in v6 and even consider migrating to another React routing module.
According to this discussion, it seems that maintainers are not focusing on nested router support during the current development timeline. But, the developer community may support maintainers to at this deprecated feature soon! Or, maintainers will offer a production-friendly workaround for this issue.
The developer community found the following workaround (rather a hack) to enable nested routers in v6, but it’s not production-friendly due to unexpected behaviors:
<UNSAFE_LocationContext.Provider value={ null }> <MemoryRouter> { /* some routes not related to the outer router */ } </MemoryRouter> </UNSAFE_LocationContext.Provider>
Subscribe to issue #7375 on GitHub and get updates related to the v6 nested routers.
You might notice unexpected behaviors, error messages, and warning messages once you complete the migration process. You may have to solve a bug due to a breaking change in v6. We can’t ship apps to production with routing issues. So, detecting routing issues with a string test plan is better.
Your React Router integration won’t work correctly after upgrading to v6 because of the following issues.
history.push
method won’t work properlyYou’ve probably updated the history
package to the latest without updating React Router to v6. The v5 history
package is compatible with React Router v6, not React Router v5. To solve this, first, update React Router, then it will automatically download a compatible history
version. If you still need to run React Router v5, use history
v4 (i.e., v4.10.1). See this issue.
x
was not found in react-router-dom
You are importing a Hook or component that was removed or renamed in v6. For example, useRouteMatch
won’t work in v6. To solve this, make sure that you don’t import deprecated v5 Hooks or components.
Make sure to add <Outlet/>
in the parent component that embeds nested route components. Check the previous Dashboard
component as a reference implementation.
x
is not a <Route>
componentIn v6, Routes
can contain only Route
or React.Fragment
children, so make sure to move other HTML elements and components to suitable places from the Routes
component.
meta.relativePath.startsWith
is not a functionYou are using an object in the path
prop of Route
. In v6, you can’t use Regex objects or arrays as a path. Make sure that you use only strings for paths.
Here we discussed some frequently occurring issues. But, you might get a unique error message you can’t find on the internet. Inspect your codebase thoroughly for deprecated API usage in such scenarios, then open a new discussion thread in React Router.
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 due to 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 makes it easy to pass global data to components and props.
When redirecting instead of using useHistory
, the new approach using useNavigate
has been designed to offer better navigation API. It allows us to set the new route easily via navigate()
instead of using multiple methods: history.push()
, history.replace()
, and history.goBack()
. Also, the new v6 API is so minimal and developer friendly. It lets you reduce boilerplate code and complex route definitions to make your app codebase clean.
Finally, the navigation link NavLink
allows us to condition active links, which makes our code neater and simpler, instead of passing two different 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.
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. Updating your code based on the above points helps you to migrate any React Router-based app. But, if your app’s routing is so complex and extensively uses deprecated v5 features, you can consider using the official incremental migration guide from maintainers.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowDeno is a popular JavaScript runtime, and it recently launched version 2.0 with several new features, bug fixes, and improvements […]
Generate OpenAPI API clients in Angular to speed up frontend development, reduce errors, and ensure consistency with this hands-on guide.
Making carousels can be time-consuming, but it doesn’t have to be. Learn how to use React Snap Carousel to simplify the process.
Consider using a React form library to mitigate the challenges of building and managing forms and surveys.
3 Replies to "Migrating to React Router v6: A complete guide"
These router overhauls, seemingly annual, are ridiculous. It’s not like they fundamentally change some obscure component, they change how routing works… Every single application has it… And again, without a clear migration path. Annoying AF.
– nested Routers are not supported(https://github.com/remix-run/react-router/issues/7375) – say, if you have with MemoryRouter or use combination of BrowserRouter + HashRouter it will not work anymore
– regexp in path is neither supported https://github.com/remix-run/react-router/issues/391
Thanks for adding these too