Routing
Most Single Page Applications will need some form of routing at some point of time, meaning that we want to map certain functions or behavior to a URL. For example, if I visited the URL of /users/manuel
, I would like to present the profile of the user manuel
.
React Router has established itself as the standard for routing in React Single Page Applications. Developed by Michael Jackson (yes, that's his name!) and Ryan Florence (who has built over 10 different routers throughout the years), it boasts with over 35,000 stars on GitHub showing its great popularity. It's well maintained, has over 500 different contributors listed on GitHub and integrates well with React principles due to its declarative nature. Moreover, it can be used on the web (client- and server-side) and in React Native. It's a very universal, well-tested and popular choice for routing.
The interface of React Router is relatively simple. In 95% of situations, you will only really encounter five core components: BrowserRouter
, Link
, Route
, Redirect
and Switch
. It also offers an imperative History API, which can be extended via the history
package which forms a thin layer over the native browser implementation making it cross-browser compatible. It also provides a Higher-Order-Component called withRouter
which allows us to pass in routing relevant data from the router to another component.
You can install React Router via:
or:
React Router's usage is declarative and can be achieved via the components mentioned above. Routers can be used anywhere in the application as long as the page tree itself is nested in a Router Context. In most cases, this context will wrap the entire application and exist only once:
Defining routes
Each component placed inside of the <Router></Router>
element, can access the Router Context, react to it or manage it. We create routes, by using the Route component and providing a path
prop as well as an optional render
or component
prop (the exception being the 404 route). The value of a render
prop has to be a function that returns a valid React element whereas the component
prop expects a component (not an element).
The correct implementation of both props can be summarized as the following:
This example would render the Example
component twice once the /example
route is hit, as the Route component merely checks whether the path of the current URL is the same as the one provided in the path
prop. This might sound a little odd at first, but there's a logical explanation.
As mentioned previously, React Router works declaratively which means that if we ask for two different routes we will also receive two components if the URL matches. This can be useful if we want to render different parts of the page independently, based on a URL. Assume that we define an App with a sidebar and a content area. Both of these should react to a URL:
In this example, we can see that two different components are rendered in different parts of our application, depending on the URL which is currently active.
However, while the above is completely valid code, one could argue that we are creating unnecessary duplication in this example. Thus, many try to avoid this structure and would rewrite the above example as the following:
While we avoided duplication of routing in this case, we have created duplication of the layout code. We should probably abstract this structure in its own layout component.
If you have tried these code examples on your own, you might have noticed something that strikes you as a little odd. React Router intentionally has a rather relaxed approach to path matching. When we hit the /account
URL, we do not only render the Account
component, but also the Home component as the /account
URL also includes the path of /
which then renders both components. This is intentional as it allows us to group certain areas of the page under a certain URL prefix and have all components render on these types of routes.
Imagine a user account and a sidebar within this user account. Let's assume that we are building a community which has a user area which is divided into different subcategories: /account/edit
to edit your profile, /account/images
to view your own pictures or /account/settings
to change your account settings. We can use a generic route in this application:
This AccountSidebar
component would now be rendered on every subpage that is part of the /account
area, as all their URLs would also include /account
.
Limit matching with props
In order to limit the matching between path
and the URL, React Router provides an exact
prop on the Route
component. If this Boolean prop is provided, the route is only rendered if the path prop exactly matches the current URL.
Where exactly you place this prop in JSX is not important. I like to place it just before the path prop to let it speak for itself: "Here's a route that matches an exact path." If we included the exact
prop in the Account Sidebar, the sidebar would only be rendered if the URL with /account
was hit, and would not register the components for /account/edit
, /account/images
or /account/settings
.
Limiting matching to a single route via Switch component
The exact
prop only ever covers a single route and does not affect other routes at all. If we have a number of URLs which could all match multiple routes, it would be very cumbersome to add an exact prop to all of these routes. React Router offers a solution to this problem though by offering the Switch
component.
The Switch
component that can wrap a number of <Route />
elements, helps us to only ever render the first route whose path
matches with the one currently present in the URL. It is not a bad idea to wrap Routes with a Switch
element by default unless you want more than one route rendered. In the above example, we could have used a Switch
component instead of the exact
prop too:
Once the /account
URL is hit, the first route would match and all other routes that follow would be ignored. In this case, only the Account
component would get rendered. Switch
components only match the URL of their direct children. If the Account
component contained its own routes, the Switch component would not take care of these. These routes would get rendered if the path
matched the current URL.
Using Switch, we can implement a 404 page as a fallback route. If we remove the path
prop from the route, the route now matches every URL. However, if it is used within a Switch
element as the very last component, we inform React Router to only ever render this component if no other component matches:
In this particular Switch
element example, we also need to provide an exact
prop for the /
route. Why? If we did not provide the exact prop, this route would always match. Even an error route like /does-not-exist
would be found under the /
route. By providing the exact
prop on the /
route, we avoid this particular problem and an error component at the end of the Switch
component can be safely rendered (if no other route matches). It fulfills a similar job to the default
case of a switch
statement in JavaScript.
Parameters in URLs
Most applications require some sort of usage of parameters in URLs. React Router also supports parameters by using colons ( : ) which some of you might be already familiar with.
We can easily restrict which parameters should be detected and can even provide further customization. For example, React Router allows the limitation of matching of routes by providing an asc
(ascending) or desc
(descending) keyword in regular expressions just after the parameter:
The above route would only match, if the URL provided was either /products/asc
or /products/desc
.
If we were to only allow numerical values in the
, we would define routes such as :userid/users/:userid(\d*)
or /users/:userid([0-9]*)
to limit these. A URL of /users/123
would lead to a render of the UserProfile
component, while /users/abc
would not.
If React Router finds such a URL, the value of the parameter is extracted and passed in to the rendered component via the match
prop.
Controlling redirects of particular routes
Apart from the usual Route
component to react to particular Routes, React Router also offers a Redirect
component. The Redirect component is initialized with a to
prop in which we can provide a destination URL that the component should redirect to. It allows us to declaratively decide in JSX where to send a particular user in certain situations. Whenever a Redirect
component is equipped with only a to
prop, a redirect to the URL provided will take place.
Redirect
components are a great solution to the common use case of having to redirect users to a login page if they are not logged in. Logged in users will continue to be directed to a Dashboard:
The render
prop of the Route component can be used to check whether a user is logged in. If they are, a Dashboard
component will be shown on the /
Route, otherwise the user will be redirected to the /login
Route via the Redirect
component.
Redirect
components can also be used inside Switch
elements. They behave just like a Route
component and only match if no other Route
or Redirect
has matched with the current URL.
If the Redirect
is used inside of a Switch
element, it can also receive a from
prop. This is the equivalent to the path
prop of the Route
component, but ensures that the Redirect
is taking place whenever the URL matches the value provided in the from prop:
The Redirect
component behaves just as the Route
component concerning URL matching. It also supports the exact
prop and also supports redirects to other routes with parameters:
If the URL /old
is hit (first example) or /users/123
(second example), the user will be redirected to the URL specified in the to
prop.
Using Router Props
Each component that was rendered by React Router and has been added as a component
prop to a Route
component, automatically receives three other props:
match
location
history
Each of these props can be accessed just like any other props. Class components can access these via this.props
whereas function components can access these with props
:
Let's have a look at the console output of this component when the route /users/123
is hit:
Without getting into too much detail for each property, we get a feel for which properties of the router we are able to access. For now, we only care about the match
property which will contain the paths
we have defined in the match.params
once a matching URL is called. In this case, we can access match.params.userid
which will yield the value of 123
.
A possible extension of this use case could be the start of an API request in a user profile component. The user id could be used to fetch the particular data for the user 123
and then display their user profile.
React Router ensures that each component connected to the Router will have a match
property which always has its own params
property. This will either contain the actual parameters or an empty object. props.match.params
is therefore safe to access without having to fear that the property might be undefined
and throwing an error.
The render
prop on the Route
component also receives the props of the router:
Navigating different routes
Once we've divided the application into different routes, we would also like to be able to link between these URLs. While we could easily use regular HTML anchors <a href="...">...</a>
, this is not recommended. Each time such an anchor is used, we would trigger a "hard" page refresh in the browser. The page would be left completely and then be loaded again.
In practice, we would ask for an HTML document which would in turn load CSS and the JavaScript containing our React application from the server again (unless it is in the browser cache). Doing this would mean that everything is re-initialized based on each new URL. Any state that might have been set globally previously would be reset.
Single Page Applications should not follow this pattern and any HTML, CSS and JavaScript should only be loaded from the server once. Global state should be persisted once we navigate through the routes and only those parts of the page which actually change, should re-render.
To facilitate said behavior, React Router supports a Link
component. It can be imported from the react-router-dom
package and also comes with a to
prop, which roughly equates to the regular href
attribute on a HTML anchor element.
React Router also uses an <a href />
under the hood. However, any clicks to this page are being intercepted and sent to an internal function which then deals with displaying new page content based on the new URL - without triggering a complete refresh of the page.
Apart from the to
prop, Links
can also be equipped with an innerRef
. These will be filled by createRef()
or useRef()
which both create a ref that can be used by the component. Moreover, Link
also supports a replace
prop which allows us to replace the current URL in the browser history instead of creating a new history entry. Be careful though, you cannot access the previous route when pressing the back button in the browser anymore if you chose to make use of the replace
prop.
Any other props that are passed to the <Link />
element, will be passed down to the generated anchor element. <Link to="/" title="Homepage">Home</Link>
would generate the following markup: <a href="/" title="Homepage">Home</a>
.
Special case: NavLink
A special form of the Link
element is the NavLink
element. Apart from the usual props that can also be received by the Link
component, NavLinks
can change based on their state. NavLinks
can access information relating to which page they are currently linking to and whether that page is the same as the current page. If this is the case, we can alter its display using activeClassName
and activeStyle
.
A classic example for this type of behavior is the overall page navigation. The currently active route in the menu is highlighted in a different color:
Only the Link of the current page receives the activeClassName
active. If we had navigated to the /account
URL, the markup would resemble the following:
NavLinks
can also receive an exact
and strict
prop (similar to the same props for the Route
component) as well as an isActive
prop. The latter expects a function which either returns true
(if the current page is the same as the one provided in NavLink
) or false
(active page is not the same as NavLink
). The function takes the aforementioned match
object as its first argument and a location
object as its second which are passed in from the router. The function can then decide whether to mark the NavLink
as active or not - based on the information available.
Navigating programmatically using the History API
We now know how we can use Route
elements on different URLs and how to react with different components. We have also learned how to avoid a full page reload by using the <Link />
element. In some cases however, it can be useful to programmatically force the change of a URL. For example, we might want to send the user to another site after successful completion of an asynchronous request.
The history
property which can be accessed via each Route
's props can help with this endeavor:
Let's look at the push()
and replace()
functions in particular. Using props.history.push('/destination')
, we can change the URL to /destination
as well as triggering a re-render. This will create a new entry in the browser's history just as using a Link
component did. If no entry in the browser's history is desired, the props.history.replace('/destination')
function can be used instead.
We also have access to a so-called props.history.go()
function which allows us to programmatically switch between entries in the browser's history. The function expects a parameter which indicates how many pages ahead or back should be turned. A negative value informs the function to go back in the entries whereas a positive value informs the function to go forward in the collection of entries. goBack()
and goForward()
are shortcuts for going back a step in the browser's history and forward. They are equivalent to calling go(-1)
and go(1)
.
The action
property in history
confirms how the user has ended up on the current page. Possible values include POP
, PUSH
or REPLACE
. POP
can either signify that the user has pressed the back button in the browser or that they have loaded the page for the first time. PUSH
informs us that the history.push()
method has been called which is also the case once a <Link />
has been clicked. If the action
properties value is REPLACE
, history.replace()
has been called or a <Link />
element has been clicked that contained a replace
property.
Connecting components with a router using HOC
Each component which is used as a component
prop in a Router
element, is automatically passed the router props history
, location
, and match
. Sometimes however, it would prove useful to not only be able to access Router functionality in components which are not using a direct Route. For example: to redirect to another page using history.push()
.
In order to allow for such use cases, React Router provides a withRouter
Higher Order Component. Each component wrapped by this HOC will automatically receive the router's props even though they are not used as a Route component:
This component is sometimes used in another situation: it can prevent "Update blocking". This used to be a necessary workaround before React Router 5.0.0. Since then however, this problem has been solved and is no longer necessary. I will still outline the reasons and rationale as to why this was necessary though should you ever find yourself working with older versions of React Router.
If components have been implemented as PureComponent
s or have been wrapped by a React.memo()
call for optimization reasons, re-renders are suppressed if no props or state have changed. As most Router components, for example NavLink
, access data in the router via React context, a PureComponent
or component wrapped by React.memo()
might not receive any information that its children need re-rendered.
In those types of situations, it is recommend to wrap these components with a withRouter()
HOC. Whenever a change in the Routing occurs and new props with a new location are passed to the respective component, a re-render will be triggered. This principle also applies to the state management library Redux. If a component is connected to the Redux store via the connect()
function, the component would prohibit the re-rendering of router-specific logic, unless something has also changed in the store.
To avoid such cases, these components should be wrapped by a withRouter()
HOC:
Since React 16.3.0 however and React Router 5.0.0, this issue has been solved and you will only come across it while working with previous versions of these two libraries.
React Router and Hooks
Since React Router 5.1.0, the library has been enhanced by a number of own Hooks that are available to the developer. It is now possible to access Router information in function components without using the withRouter()
HOC provided that these components are not directly used as argument for the <Route />
element prop component (<Route component={MyComponent} />
).
As is the case with most Hooks, using React Router's own Hooks is a very pleasant and straightforward experience. Four Hooks allow us to access the location
object, the history
instance, the Route parameter or the match
object. Conveniently, these Hooks are aptly called useLocation
, useHistory
, useParams
and useRouteMatch
. In order to use these Hooks, the component intended for the Hooks implementation needs to be be nested within a <Router>
tree. However, it is not necessary for the component to follow directly after the Router element or be placed on the most upper layer. These Hooks can access the Router context and can thus be used anywhere in your application.
useLocation()
First, let's have a closer look at the arguably simplest Hook in our list. We can import it via the react-router-dom
package. Once installed, we can access location data by using the return value of the Hook:
In this example, we would obtain the following output for the showLocationInfo
component:
useHistory()
This Hook allows us to use the history
instance of React Router. It can access and change the URL via the push()
and replace()
methods and thus trigger a re-render of the application. goBack()
, goForward()
as well as the more general go()
can be used to navigate the browser's history.
This example demonstrates how we can implement a button which will direct us to the home page once it's clicked using the useHistory()
Hook.
useParams()
This Hook is basically a shortcut for accessing parameters which were hidden in match.params
. If a route has been implemented using placeholders, such as /users/:userid
, and then a URL such as /users/123
has been called, the params
object will contain a key/value pair in the form of { "userid": "123" }.
The useParams()
Hook allows us to directly access this object:
If a route such as /users/:userid
had been created and a URL such as /users/123
has been called, the output would look like this:
useRouteMatch()
The last Hook introduced by React Router 5.1.0 forms the useRouteMatch()
Hook. It allows us to access the whole match
object of a Route, meaning we can now obtain information on params
, url
, path
and isExact
. We can now safely check whether the URL matches the path
of a route.
The function can take in a path which will in turn return a match
object for the route. If no particular path is provided to the function, the path of the current route will be used. Using our previous example with the path of /users/:userid
, the Route <Route path="/users/:userid">
will return the following match
object if the URL /users/123
is provided as an argument:
If the Hook is called with a path that does not match with the current route, null
will be returned from the function:
Calling this function with /users/:userid
will return a value of null
.
Last updated