See Our Public 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 useregular `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:
// beforeimport { Route } from 'react-router-dom'function App() {return (<div>{/* ... */}<Routepath="/BLOG/:slug/"strictsensitiverender={({ match }) => {return match ? <BlogPost match={match} /> : <NotFound />}}/></div>)}// afterimport { 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:
// beforefunction BlogPost({ match, location, history }) {let { slug } = match.params// ...}// afterfunction 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} /><Routepath="/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, althoughwithRouter
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 withuseRouteMatch
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