Router V4 | Quick Recap
React Router 4 is latest version of the official routing library for React. When building Single Page Applications or SPAs, you need client side routing so you can navigate through your React application UI which usually contains multiple pages or views.
React Router 4 allows you to keep your application UI and the URL in synchronization -- so in this section will to quick recap of how to use React Router 4 and what you can achieve with the declarative routing approach.
An example, here's the application in v4:
The first thing that stands out when looking at an app built with React Router v4 is that the "router" seems to be missing. In v3 the router was this giant thing we rendered directly to the DOM which orchestrated our application. Now, besides <BrowserRouter>
, the first thing we throw into the DOM is our application itself.
Another v3-staple missing from the v4 example is the use of {props.children}
to nest components. This is because in v4, wherever the <Route>
component is written is where the sub-component will render to if the route matches.
Inclusive Routing
In the previous example, you may have noticed the exact
prop. So what's that all about? V3 routing rules were "exclusive" which meant that only one route would win. V4 routes are "inclusive" by default which means more than one <Route>
can match and render at the same time.
In the previous example, we're trying to render either the HomePage
or the UsersPage
depending on the path. If the exact
prop were removed from the example, both the HomePage
and UsersPage
components would have rendered at the same time when visiting `/users` in the browser.
To demonstrate how inclusive routing is helpful, let's include a UserMenu
in the header, but only if we're in the user's part of our application:
Now, when the user visits `/users`, both components will render. Something like this was doable in v3 with certain patterns, but it was more difficult. Thanks to v4's inclusive routes, it's now a breeze.
Exclusive Routing
If you need just one route to match in a group, use <Switch>
to enable exclusive routing:
Only one of the routes in a given <Switch>
will render. We still need exact
on the HomePage
route though if we're going to list it first. Otherwise the home page route would match when visiting paths like `/users` or `/users/add`. In fact, strategic placement is the name-of-the-game when using an exclusive routing strategy (as it always has been with traditional routers). Notice that we strategically place the routes for /users/add
before /users
to ensure the correct matching. Since the path /users/add
would match for `/users` and `/users/add`, putting the /users/add
first is best.
Sure, we could put them in any order if we use exact
in certain ways, but at least we have options.
The <Redirect>
component will always do a browser-redirect if encountered, but when it's in a<Switch>
statement, the redirect component only gets rendered if no other routes match first. To see how <Redirect>
might be used in a non-switch circumstance, see Authorized Route below.
"Index Routes" and "Not Found"
While there is no more <IndexRoute>
in v4, using <Route exact>
achieves the same thing. Or if no routes resolved, then use <Switch>
with <Redirect>
to redirect to a default page with a valid path (as I did with HomePage
in the example), or even a not-found page.
Nested Layouts
You're probably starting to anticipate nested sub layouts and how you might achieve them. I didn't think I would struggle with this concept, but I did. React Router v4 gives us a lot of options, which makes it powerful. Options, though, means the freedom to choose strategies that are not ideal. On the surface, nested layouts are trivial, but depending on your choices you may experience friction because of the way you organized the router.
To demonstrate, let's imagine that we want to expand our users section so we have a "browse users" page and a "user profile" page. We also want similar pages for products. Users and products both need sub-layout that are special and unique to each respective section. For example, each might have different navigation tabs. There are a few approaches to solve this, some good and some bad. The first approach is not very good but I want to show you so you don't fall into this trap. The second approach is much better.
For the first, let's modify our PrimaryLayout
to accommodate the browsing and profile pages for users and products:
While this does technically work, taking a closer look at the two user pages starts to reveal the problem:
Each user page not only renders its respective content but also has to be concerned with the sub layout itself (and the sub layout is repeated for each). While this example is small and might seem trivial, repeated code can be a problem in a real application. Not to mention, each time a BrowseUsersPage
or UserProfilePage
is rendered, it will create a new instance of UserNav
which means all of its lifecycle methods start over. Had the navigation tabs required initial network traffic, this would cause unnecessary requests — all because of how we decided to use the router.
Here's a different approach which is better:
Instead of four routes corresponding to each of the user's and product's pages, we have two routes for each section's layout instead.
Notice the above routes do not use the exact
prop anymore because we want /users
to match any route that starts with /users
and similarly for products.
With this strategy, it becomes the task of the sub layouts to render additional routes. Here's what the UserSubLayout
could look like:
The most obvious win in the new strategy is that the layout isn't repeated among all the user pages. It's a double win too because it won't have the same lifecycle problems as with the first example.
One thing to notice is that even though we're deeply nested in our layout structure, the routes still need to identify their full path in order to match. To save yourself the repetitive typing (and in case you decide to change the word "users" to something else), use props.match.path
instead:
Match
match.path vs match.url
The differences between these two can seem unclear at first. Console logging them can sometimes reveal the same output making their differences even more unclear. For example, both these console logs will output the same value when the browser path is `/users`:
While we can't see the difference yet, match.url
is the actual path in the browser URL and match.path
is the path written for the router. This is why they are the same, at least so far. However, if we did the same console logs one level deeper in UserProfilePage
and visit `/users/5` in the browser, match.url
would be "/users/5"
and match.path
would be "/users/:userId"
.
Which to choose?
If you're going to use one of these to help build your route paths, I urge you to choose match.path
. Using match.url
to build route paths will eventually lead a scenario that you don't want. Here's a scenario which happened to me. Inside a component like UserProfilePage
(which is rendered when the user visits `/users/5`), I rendered sub components like these:
To illustrate the problem, I'm rendering two sub components with one route path being made from match.url
and one from match.path
. Here's what happens when visiting these pages in the browser:
Visiting `/users/5/comments` renders "UserId: undefined".
Visiting `/users/5/settings` renders "UserId: 5".
So why does match.path
work for helping to build our paths and match.url
doesn't? The answer lies in the fact that {${match.url}/comments}
is basically the same thing as if I had hard-coded {'/users/5/comments'}
. Doing this means the subsequent component won't be able to fill match.params
correctly because there were no params in the path, only a hardcoded 5
.
match:
path - (string) The path pattern used to match. Useful for building nested
<Route>
surl - (string) The matched portion of the URL. Useful for building nested
<Link>
s
Let's assume the app we're making is a dashboard so we want to be able to add and edit users by visiting `/users/add` and `/users/5/edit`. But with the previous examples, users/:userId
already points to a UserProfilePage
. So does that mean that the route with users/:userId
now needs to point to yet another sub-sub-layout to accomodate editing and the profile? I don't think so. Since both the edit and profile pages share the same user-sub-layout, this strategy works out fine:
Notice that the add and edit routes strategically come before the profile route to ensure there the proper matching. Had the profile path been first, visiting `/users/add` would have matched the profile (because "add" would have matched the :userId
.
Authorized Route
It's very common in applications to restrict the user's ability to visit certain routes depending on their login status. Also common is to have a "look-and-feel" for the unauthorized pages (like "log in" and "forgot password") vs the "look-and-feel" for the authorized ones (the main part of the application). To solve each of these needs, consider this main entry point to an application:
There are a few takeaways with this approach. The first being that I'm choosing between two top-level layouts depending on which section of the application we're in. Visiting paths like `/auth/login` or `/auth/forgot-password` will utilize the UnauthorizedLayout
— one that looks appropriate for those contexts. When the user is logged in, we'll ensure all paths have an `/app` prefix which uses AuthorizedRoute
to determine if the user is logged in or not. If the user tries to visit a page starting with `/app` and they aren't logged in, they will be redirected to the login page.
While your login strategy might differ from mine, I use a network request to getLoggedUser()
and plug pending
and logged
into Redux state. pending
just means the request is still in route.
Other mentions
There's a lot of other cool aspects React Router v4. To wrap up though, let's be sure to mention a few small things so they don't catch you off guard.
#<Link> vs <NavLink>
The use of <NavLink>
allows me to set a class of active
to whichever link is active. But also, notice that I can use exact
on these as well. Without exact
the home page link would be active when visiting `/app/users` because of the inclusive matching strategies of v4. In my personal experiences, <NavLink>
with the option of exact
is a lot more stable than the v3 <Link>
equivalent.
Last updated
Was this helpful?