Blog Hero Background

React Router v5.1


User Avatar
Michael JacksonSay hi on Twitter
September 24, 2019
ReactReact Router

Upcoming Workshops

Today, we are releasing React Router version 5.1. This post will discuss the 5.1 release as well as discuss a few things you can do to prepare for the future.

We are hard at work incorporating the best ideas from @reach/router, as well as community feedback about the 4/5 API, into version 6 which we expect will be ready sometime around the beginning of the new year. That being said, we are putting a lot of emphasis on making a silky smooth migration path for anyone who wants to stay up to date as we get closer to v6. As we introduce new features and APIs, we will let you know at every step of the way what you can do to make the transition as easy as possible.

Meet React Router 5.1

Easily the most notable feature in this release is the addition of some hooks (for React 16.8 users, ofc). We are excited about the ability that hooks give us to compose state and behavior instead of just composing elements.

At its heart, React Router is a state container for the current location, or URL. It keeps track of the location and renders different <Route>s as it changes, and it also gives you tools to update the location using <Link>s and the history API. Given the fact that managing this piece of state is the router's main responsibility, you can imagine that a new primitive that lets us compose state is going to enable a few things we couldn't do before!

Let's explore the new hooks 1 by 1, and we'll follow it up with some tips about how to get the most out of this release while getting ready for the future of React Router.

useParams

First up is useParams. In React Router, we use the word "params" to describe dynamic segments of the URL. Before now, if you wanted to access the current params, you had to get them either through the match prop passed to your component or through the arguments to our render prop, like this:

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
// In the code below, BlogPost is used as both a <Route component>
// and in a <Route render> function. In both cases, it receives a `match`
// prop, which it uses to get the URL params.
function BlogPost({ match }) {
let { slug } = match.params
// ...
}
ReactDOM.render(
<Router>
<div>
<Switch>
{/* Using the `component` prop */}
<Route path="/blog/:slug" component={BlogPost} />
{/* Using the `render` prop */}
<Route path="/posts/:slug" render={({ match }) => <BlogPost match={match} />} />
</Switch>
</div>
</Router>,
document.getElementById('root')
)

Both of these methods are fine for getting the current URL params, but they both leave something to be desired. In the case of the component prop, the router actually has to createElement(component) for you, which prevents you from passing in custom props to your <BlogPost> element. In the case of the render prop, you can pass in custom props but you're forced to manually pass along the values you get from the callback to your element.

In addition, both methods only provide access to the params in <BlogPost>, so you're forced to manually pass them along to components further down the tree if you need them.

Let's see how useParams fixes all of these problems for us:

import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Switch, useParams } from 'react-router-dom'
function BlogPost() {
// We can call useParams() here to get the params,
// or in any child element as well!
let { slug } = useParams()
// ...
}
ReactDOM.render(
<Router>
<div>
<Switch>
{/* No weird props here, just use
regular `children` elements! */}
<Route path="/posts/:slug">
<BlogPost />
</Route>
</Switch>
</div>
</Router>,
document.getElementById('root')
)

Notice 2 things:

  • We don't have to do any weird component composition tricks to get the params, we can just use regular children elements
  • We can useParams() anywhere in <BlogPost> or its subtree without manually passing params around

There is also another really nice benefit here for TypeScript users: no more typing component and/or render props! You already know how to type regular React children.

Of course, these are really just benefits of using hooks, so they'll apply to all of the hooks we discuss here. But it's really interesting to see the practical benefits of hooks in action (we are a training company after all, so we kind of nerd out on this stuff 🤓)!

useLocation

Another hook in the 5.1 release is useLocation, which returns the current location object. This is useful any time you need to know the current URL.

For example, imagine a useEffect hook where you want to send a "page view" event to your web analytics service every time the URL changes. With useLocation, it's as easy as:

import { Switch, useLocation } from 'react-router-dom'
function usePageViews() {
let location = useLocation()
useEffect(() => {
ga.send(['pageview', location.pathname])
}, [location])
}
function App() {
usePageViews()
return <Switch>{/* your routes here */}</Switch>
}

useHistory

For programmatic navigation purposes, we provide access to the history object via useHistory.

import { useHistory } from 'react-router-dom'
function BackButton({ children }) {
let history = useHistory()
return (
<button type="button" onClick={() => history.goBack()}>
{children}
</button>
)
}

Heads up: The useHistory hook is a quick stopgap for a future hook that we are working on: useNavigate. useNavigate will provide an API that is more closely aligned with <Link> and will fix a few long-standing problems with using the history API directly in the router (it'll probably look a lot like @reach/router's navigate API). We are providing useHistory for now to make the migration of existing code that uses the history API as painless as possible.

useRouteMatch

The useRouteMatch hook is useful any time you are using a <Route> just so you can get access to its match data, including all of the times you might render a <Route> all by itself outside of a <Switch>. It matches the URL exactly like a <Route> would, including the exact, strict, and sensitive options.

So instead of rendering a <Route>, just useRouteMatch instead:

// before
import { Route } from 'react-router-dom'
function App() {
return (
<div>
{/* ... */}
<Route
path="/BLOG/:slug/"
strict
sensitive
render={({ match }) => {
return match ? <BlogPost match={match} /> : <NotFound />
}}
/>
</div>
)
}
// after
import { useRouteMatch } from 'react-router-dom'
function App() {
let match = useRouteMatch({
path: '/BLOG/:slug/',
strict: true,
sensitive: true,
})
return (
<div>
{/* ... */}
{match ? <BlogPost match={match} /> : <NotFound />}
</div>
)
}

Just like <Route>, if you omit the path this hook will return the match from the closest matching <Route> in the tree.

Staying Ahead of The Curve

If you'd like to get a head start on upgrading to the next major version of React Router, there are a few things you can do today assuming you're on both React >= 16.8 and React Router 5.1. A few important points here:

  • All of these steps are optional. You can make zero changes to your app and use 5.1. These steps are just going to give you a bit of a head start on upgrading to React Router 6.
  • You don't have to migrate all of your route components at once! You can do just a few of them now and come back for the rest later.

First of all, update your route components. That means changing:

// before
function BlogPost({ match, location, history }) {
let { slug } = match.params
// ...
}
// after
function BlogPost() {
let { slug } = useParams()
let location = useLocation()
let history = useHistory()
// ...
}

You can do this to one component, ship it, go home and enjoy your weekend. No need to do them all at once.

Then, if you're feeling ambitious, you can go ahead and update some of your <Switch> configurations to use regular <Route children> instead of component and/or render props for the route components that are now using hooks:

// before
<Switch>
<Route path="/blog/:slug" component={BlogPost} />
<Route
path="/posts/:slug"
render={({ match }) => <BlogPost match={match} other="props" />}
/>
</Switch>
// after
<Switch>
<Route path="/blog/:slug">
<BlogPost />
</Route>
<Route path="/posts/:slug">
<BlogPost other="props" />
</Route>
</Switch>

We encourage you to get used to using <Route children> instead of <Route component> and <Route render>. That way, you can enjoy using JSX to compose your elements and hooks to compose your state!

Although they are not deprecated in 5.1, the <Route component> and <Route render> APIs have several quirks that just aren't needed (see the discussion in useParams above) in a world with hooks. We will most likely deprecate these APIs in a future release.

If you'd like to do even more, go ahead and:

  • Replace any withRouter usage with hooks. They give you access to all the same stuff. Again, although withRouter is not deprecated in 5.1, it's a weird API when you can compose your state with hooks instead. It will also most likely be deprecated in a future release.
  • Replace any "floating" (not inside a <Switch>) <Route> elements with useRouteMatch

And ... that's it! Again, all of these steps are optional, but they should go a long way in getting your app ready for our next major version.

We sincerely hope you enjoy React Router 5.1, and we look forward to announcing and shipping even more improvements very soon!


Instead of having comments here in our blog, we've tweeted about this post so you can comment there if you wish.

View on Twitter

Subscribe for updates on public workshops and blog posts

Don't worry, we don't send emails too often.

i love react
© 2024 ReactTraining.com