Webpage navigation is something that is intrinsic to almost every website that goes past “Hello World.” In this blog, I will be exploring React-Router, a client and server-side routing library that can run anywhere React can. More specifically, I will be focusing on what changed with the new React Router v6 version.
This blog will assume some basic React knowledge as well as a general understanding of webpage routing but will not require any in-depth knowledge of either to understand.
What’s New with React Router?
React Router has been around for quite a long time and can be considered a core addition to many React projects. With the recent v6 release, React Router consolidated years of feedback and technological improvements to deliver the most streamlined user experience to date. It is worth noting, however, that since v6 of React Router was built from the ground up using hooks, it will require React 16.8 or greater.
Below, I will go over some of the major upgrades of React Router v6 compared to prior versions as well as what they mean for you as a developer.
No More Route Ordering
React Router will now match to the most specific defined route when multiple routes match. Previously, if you had the URL /keyhole/welcome
, it would match to both of the following routes:
<Route path="keyhole/:userId” element={<KeyholeUser />} /> <Route path="keyhole/welcome” element={<KeyholeWelcome />} />
In React Router v6, however, it will match to /keyhole/welcome
, as it is a more specific match, regardless of route order. This is similar to v5 when the exact
keyword was used.
Route Element
The element
keyword replaces the render/component
keywords, and children are now used exclusively for nested routing. This also means that the Route element
will take in a React element rather than a component.
What does this mean for you, though? Essentially, it boils down to an ease of use scenario with properties. While both v5 and v6 can handle passing properties to component via Route
, this change simplifies the process and removes some of the boilerplate issues present before.
// v5 before 5.1 examples // No props <Route path="/keyhole" component={Keyhole} /> // Route with props <Route path="/keyhole" render={customProps => ( <Profile {…customProps} /> )} /> // v6 examples // No props <Route path="/keyhole" element={<Keyhole />} /> // With Props <Route path="/keyhole" element={<Keyhole {…customProps} />} />
React Router v5.1 also started using React elements rather than components, just with a slightly different layout. Furthermore, React elements over components appears to be the direction that React itself is taking if React Suspense is any indication.
Nested/Relative Routing
Routes can now nest within one another, allowing you to break down your code into easier-to-read segments. Take the following example:
<Routes> <Route path="keyhole” element={<Keyhole />}> <Route path=“:userId” element={<User />} /> <Route path="welcome” element={<Welcome />} /> </Route> </Routes>
This would render 3 routes, /keyhole
, /keyhole/welcome
, and /keyhole/:userId
where the two nested routes inherit the parent /keyhole
base. This structure also breaks down the overall project layout and allows for an easier way to picture how each piece of the codebase puzzle fits together all the way from the base top-level App
down to the smallest button
.
It is important to note, however, that in order for this to render properly, the parent route element does need an Outlet
to define where the child routes will render at. This will be somewhat similar to Switch
from previous versions but much simpler. You will not need any routes defined in the component, just the Outlet
to tell it where to render the component defined in the Routes
block, as seen above.
Below is an example of how the Keyhole
component could look in v6.
<div> <h1>Keyhole</h1> <Outlet /> </div>
Relative Links
Similar to Relative routes, Link
components also automatically inherit the parent route as long as they are not prefixed with a /
. Using the Relative Routing example above, we could add a link to the example Keyhole
component to allow for navigation to one of its child routes as follows.
// Component at the "/keyhole" URL <div> <h1>Keyhole</h1> // Would link to the url "/keyhole/welcome" <Link to=“welcome”>Welcome</Link> <Outlet /> </div>
Descendent Routes
Branching from the Nested/Relative routes section above, it is now possible to use multiple Routes
elements and have them inherit a parent route defined in a higher level Routes
block. This is especially useful for larger applications. It means that all routes do not all have to be defined in a single Routes element or repeat parent URLs. An example of this is below.
<Routes> <Route path="/" element={<MainPage />} /> <Route path=“keyhole/*” element={<Keyhole />} /> </Routes> // Within the Keyhole component, all routes start with "/keyhole" <Routes> <Route path="/" element={<KeyholeWelcome />} /> <Route path=“:userId” element={<User/>} /> </Routes>
Note that within the /keyhole/*
route, there is a trailing *
. This signals to React Router that there can be child routes that build off of it, and in the case of the example, build into KeyholeWelcome
or User
depending on which URL in the child element is hit.
useNavigate
has replaced useHistory
useHistory
has been replaced with useNavigate
, and effectively all of the functionality is intact. There are two main differences. One, the history.push(url)
has been replaced with just navigate(url)
. Two, forwards and backward navigations with go(#)
have been replaced with navigate(#)
, where a positive number indicates forwards and a negative backward.
The React Router v6 upgrade documentation has an in-depth list of changes, but I have outlined the main use cases below.
// v5 examples let history = useHistory(); // Navigate to new URL history.push(“/keyhole”); // v5 Navigating forwards or backwards on browser history const { go, goBack, goForward } = useHistory(); <button onClick={() => go(-2)}>Go 2 pages back</button> <button onClick={goBack}>Go back</button> <button onClick={goForward}>Go forward</button> <button onClick={() => go(2)}>Go 2 pages forward</button> // v6 examples let navigate = useNavigate(); // Navigate to new URL navigate(“/keyhole”) // v6 Navigating forwards or backwards on browser history const navigate = useNavigate(); <button onClick={() => navigate(-2)}>Go 2 pages back</button> <button onClick={() => navigate(-1)}>Go back</button> <button onClick={() => navigate(1)}>Go forward</button> <button onClick={() => navigate(2)}>Go 2 pages forward</button>
As you can see, both v5 and v6 are fairly similar, but due to v6 using the useTransition
hook as an underlying base, it can better handle when user input interrupts a pending route transition.
Index Routes
Index routes can be thought of as “default child routes.” This means that they will only function if no child route matches the URL.
Here’s an example of when this would be useful. You have a Parent route that you want other child routes to render with but need the user to select a child route first. Thanks to index routes, you can add an index component that will only render until the user chooses a path.
Below, I’ve built an example of what this would look like in a Routes
component
<Routes> <Route path="/" element={<Keyhole />}> <Route index element={<SelectRoute />} /> <Route path=“newUser” element={<NewUser />} /> <Route path=“existingUser” element={<ExistingUser />} /> </Route> </Routes>
Not Found Routes
Unlike React Router v5, v6 now has a clearly defined path for the default “Not Found” route in a given set of Routes
. This Not Found route will only be picked if no routes match.
Note that this is different from the index route above, as the “Not Found” route only renders when no routes match whereas the index route will only render if the parent URL is valid but no child routes match.
// v5 <Switch> <Route exact path="/"> <Keyhole /> </Route> <Route path="/welcome"> <Welcome /> </Route> <Route> <NotFound /> </Route> </Switch> // v6 <Routes> <Route path="/" element={<Keyhole />} /> <Route path=“welcome” element={<Welcome />} /> <Route path="*" element={<NotFound />} /> </Routes>
Upgrading to v6
Now that we’ve seen some of the new and exciting features included with v6, it’s time to ask the question: How do I upgrade?
Luckily, while upgrading a dependency is not always easy, React Router tries its best to streamline the process as much as possible. While I will not be going in-depth into the v6 upgrade, the upcoming links should answer any questions you may have about the process.
To see the upgrade process from v5 to v6 of React Router, I recommend using the guide here. For those of you making the switch from Reach Router, there is also a guide here, as React Router v6 is the successor to Reach Router as well.
One final thing to note when upgrading to React Router v6 is that the team that created both Reach and React Router is currently working on a backward compatibility package to ease the transition for those of you working on large codebases.
Wrapping Up
Creating a multi-page React application can be a daunting proposition when you first think about it. But, with React Router v6, it’s simpler than ever. Now that we’ve explored some of the new ways that React Router can help as well as some ways in which existing functionality has been improved, I encourage you to check it out yourself!
If you are new to the React Router space but want to get your feet wet, this tutorial is a great resource to get you up and running as fast as possible. And for those who already have experience with React Router, the React Router v6 documentation and the v6 Release Post are great resources for everything from APIs to upgrade guides to reasons why certain changes were made.
Thanks for sticking around all the way to the end! I hope that the improvements to React Router got you as excited as they did me for the future of React development.