Until now we've been on React Router V1. While some parts of the API look similar between the two versions they are very different in a lot of ways.
First of all, there are now two packages for React Router. In a similar way to how react-dom
was split from react
, react-router
is now the core (cross-platform) library with react-router-dom
being the DOM specific implementation. react-router
contains all the general routing components:
<Router />
<Route />
<Redirect />
While react-router-dom
contains all the components that render actual DOM elements
<Link />
<NavLink />
The big difference with the V5 is that <Route />
components can now be rendered anywhere in the JSX tree. They previously had to sit directly under a Router. So before we had one file where all our routes were defined:
<Router>
<Route path="/foo" component={Foo} />
<Route path="/bar" component={Bar} />
<Route path="/foo/bar" component={FooBar} />
</Router>
We can now be less centralized and nest routes in our own components. Let's take a look at the admin section as an example. Our old routes looked like this:
// routes.js
<Route path="/admin(/)" name="admin" component={Admin}>
<Route path="people(/)" component={PeoplePage} />
<Route path="groups/:groupId(/)" component={SingleGroupPage}>
<Route path="dashboards(/)" component={DashboardsGroupPage} />
<Route path="data-sources(/)" component={DataSourcesGroupPage} />
<Route path="loops(/)" component={LoopsGroupPage} />
</Route>
</Route>
Now, all the routing could happen from the <Admin />
component
// routes.js
<Route path="/admin" component={Admin} />
// admin-component.js
<div>
<SharedAdminHeader />
<Route path="/admin/people" component={PeoplePage} />
<Route path="/admin/groups/:groupId/dashboards" component={DashboardsGroupPage}/>
<Route path="/admin/groups/:groupId/data-sources" component={DataSourcesGroupPage}/>
<Route path="/admin/groups/:groupId/loops" component={LoopsGroupPage}/>
</div>
There are now 3 ways to render a component from a <Route />
.
This is the same as Router V1, you pass a component constructor and when the path of the route matches the component will be rendered and passed 3 props
location
an object describing the current locationhistory
the history objectmatch
an object that contains all the matching url parameter values
<Route path="/admin/people" component={PeoplePage} />
This is useful if you want to take values from the URL and pass them straight to the component without the component having to care about the URL. When the path of the route matches it will call the render function with the same 3 props as above and will render the returned value.
<Route
path="/admin/groups/:groupId/dashboards"
render={({ match, location, history}) => (
<DashboardGroupsPage groupId={match.params.groupId} />
)}
/>
You can pass a function as a child, this is almost exactly the same as the render
prop except that this function will be called even when the path isn't matching. If the path isn't matching the match
value passed to the function will be null
. This can be useful for rendering modals for example, where you always want to render the component but want to switch the isVisible
prop on when the path matches. This was previously very fiddly to do. Now it's easy!
<Route
path="/admin/groups/:groupId/users/invite"
>
{({ match, location, history}) => (
<UserInviteModal isVisible={!!match} groupId={match && match.params.groupId} />
)}
</Route>
By default, any route that matches the path will be rendered, that means a number of matching routes could be rendered at the same time. In cases, like the admin page, where you only want to render a single child you can use the <Switch />
component, and it will only ever render the first matching route.
Here's an interesting case:
<Route path="/edit/dashboards/new" component={NewDashboard} />
<Route path="/edit/dashboards/:dashboardId" component={EditDashboard} />
In Router V5 the url /edit/dashboards/new
would match both routes, in the second case the value of match.params.dashboardId
would be the string 'new'
. And both the NewDashboard
and EditDashboard
components would be rendered. Obviously we only want to render the NewDashboard
component so we would wrap them in a <Switch />
.
<Switch>
<Route path="/edit/dashboards/new" component={NewDashboard} />
<Route path="/edit/dashboards/:dashboardId" component={EditDashboard} />
</Switch>
Now when the URL is /edit/dashboards/new
it matches the first route so the switch component stops looking any further and renders the NewDashboard
. When the URL is /edit/dashboards/123
it doesn't match the first route but does match the second and so the EditDashboard
component is rendered.
That's all the important parts. Also remember. Rendering an <a>
tag will completely avoid any client side routing and will hard refresh the page. When navigating inside the app always try to use <Link />