Esteban Herrera Family man, #Java and #Javascript developer. #Swift, and #VR/#AR hobbyist. Like #books, #movies and still trying many things. eherrera.net

React Router DOM: set-up, essential components, & parameterized routes

10 min read 2814


Recently, Yomi did a great job writing about advanced React Router concepts.

But if you’re just starting out with React Router, that post was probably a little too much to wrap your head around.

Not to worry.

In this post, I’ll get you started with the basics of the web version (React Router DOM).

Here’s what you’ll learn:

 
  • The concept of a router
  • How to setup and install React Router
  • The essential components of this framework
  • How to build routes with parameters, like /messages/10

The demo app for this post is available on CodeSandbox:

For reference, you can find the code of the final example on this GitHub repository.

Let’s get started.

What is a router?

Single-page applications (SPAs) rewrite sections of a page rather than loading entire new pages from a server.

Twitter is a good example of this type of application. When you click on a tweet, only the tweet’s information is fetched from the server. The page does not fully reload:

These applications are easy to deploy and greatly improve the user experience, among other advantages.

However, they also bring challenges.

One of them is browser history. As the application is contained in a single page, it cannot rely on the browser’s forward/back buttons per se. It needs something else.

Something that, according to the application’s state, changes the URL to push or replace URL history events within the browser. At the same time, it also needs to rebuild the application state from information contained within the URL.

On Twitter, for example, notice how the URL changes when a tweet is clicked:

And how a history entry is generated:

This is the job of a router.

A router allows your application to navigate between different components, changing the browser URL, modifying the browser history, and keeping the UI state in sync.

React is a popular library for building SPAs. However, as React focuses only on building user interfaces, it doesn’t have a built-in solution for routing.

React Router is the most popular routing library for React. It allows you define routes in the same declarative style:

<Route path="/home" component={Home} />

But let’s not get ahead of ourselves. Let’s start by creating a sample project and setting up React Router.

Setting up React Router

I’m going to use Create React App to create a React app. You can install (or update) it with:

npm install -g create-react-app

You just need to have Node.js version 6 or superior installed.

Next, execute the following command:

create-react-app react-router-example

In this case, the directory react-router-example will be created. If you cd into it, you should see a structure similar to the following:

React Router includes three main packages:

  • react-router. This is the core package for the router.
  • react-router-dom. It contains the DOM bindings for React Router. In other words, the router components for websites.
  • react-router-native. It contains the React Native bindings for React Router. In other words, the router components for an app development environment using React Native.

Since this is a web app, you’ll have to install react-router-dom:

npm install — save react-router-dom

At this point, you can execute:

npm start

A browser window will open http://localhost:3000/ and you should see something like this:

Now let’s create a simple SPA with React and React Router.

Using React Router

The React Router API is based on three components:

  • <Router>. The router that keeps the UI in sync with the URL.
  • <Link>. Renders a navigation link.
  • <Route>. Renders a UI component depending on the URL.

<Router>

Only in some special cases you’ll have to use <Router> directly (for example when working with Redux), so the first thing you have to do is to choose a router implementation.

In a web application, you have two options:

If you’re going to target older browsers that don’t support the HTML5 History API, you should stick with <HashRouter>, which creates URLs with the following format:

http://localhost:3000/#/route/subroute

Otherwise, you can use <BrowserRouter>, which creates URLs with the following format:

http://localhost:3000/route/subroute

I’ll use <BrowserRouter>, so in src/index.js I’m going to import this component from react-router-dom and use it to wrap the <App> component:

No Title

No Description

It’s important to mention that a router component can only have one child element. For example, the following code:

No Title

No Description

Throws this error message:

The main job of a <Router> component is to create a history object to keep track of the location (URL). When the location changes because of a navigation action, the child component (in this case <App>) is re-rendered.

Most of the time, you’ll use a <Link> component to change the location.

<Link>

Let’s create a navigation menu.

Open src/App.css to add the following styles:

No Title

No Description

In scr/App.js, replace the last <p> element in the render() function so it looks like this:

No Title

No Description

Don’t forget to import the <Link> component at the top of the file:

import {
Link
} from 'react-router-dom'

In the browser, you should see something like this:

As you can see, this JSX code:

<ul>
<li> <Link to="/">Home</Link> </li>
<li> <Link to="/messages">Messages</Link> </li>
<li> <Link to="/about">About</Link> </li>
</ul>

Generates the following HTML code:

<ul>
<li> <a href="/">Home</a> </li>
<li> <a href="/messages">Messages</a> </li>
<li> <a href="/about">About</a> </li>
</ul>

However, those aren’t regular anchor elements. They change the URL without refreshing the page. Test it.

And now add a <a> element to the JSX code and test one more time:

<ul>
<li> <Link to="/">Home</Link> </li>
<li> <Link to="/messages">Messages</Link> </li>
<li> <Link to="/about">About</Link> </li>
<li>
    <a href="/messages">Messages (with a regular anchor element)</a> 
  </li>
</ul>

Do you notice the difference?

<Route>

Right now, the URL changes when a link is clicked.

But not the UI. Let’s fix that.

I’m going to create three components for each route. First, src/component/Home.js for the route /:

No Title

No Description

Then, src/component/Messages.js for the route /messages:

No Title

No Description

And finally, src/component/About.js for the route /about:

No Title

No Description

To specify the URL that corresponds to each component, you use the <Route> in the following way:

<Route path="/" component={Home}/>
<Route path="/messages" component={Messages}/>
<Route path="/about" component={About}/>

With other router libraries (and even in previous versions of React Router), you have to define these routes in a special file, or at least, outside your application.

This doesn’t apply to React Router 4. These components can be placed anywhere inside of the router, and the associated component will be rendered in that place, just like any other component.

So in src/App.js, import all these components and add a section after the menu:

In the browser, you should see something like this:

However, look what happen when you go to the other routes:

By default, routes are inclusive, more than one <Route> component can match the URL path and render at the same time.

Routes are the most important concept in React Router, and you need to know some things about them.

Let’s talk about routes in the next section.

Understanding routes

The matching logic of the <Route> component is delegated to the path-to-regexp library. I encourage you to check all the options and modifiers of this library and test it live with the express-route-tester.

In the previous example, since the /message and /about paths also contain the character /, they are also matched and rendered.

With this behavior, you can display different components just by declaring that they belong to the same (or similar) path.

There’s more than one solution.

The first one uses the exact property to render the component only if the defined path matches the URL path exactly:

<Route exact path="/" component={Home}/>

If you test the application, you’ll see that everything works fine.

The routes /message and /about are still evaluated, but they are not an exact match for / now.

However, if we know that only one route will be chosen, we can use a <Switch> component to render only the first route that matches the location:

<Switch> will make the path matching exclusive rather than inclusive (as if you were using <Route> components).

For example, even if you duplicate the route for the Messages component:

<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/messages" component={Messages} />
  <Route path="/messages" component={Messages} />
  <Route path="/about" component={About} />
</Switch>

When visiting the /messages path, the Messages component will be rendered only once.

However, notice that you’ll still need to specify the exact property for the /path, otherwise, /message and /about will also match /, and the Homecomponent will always be rendered (since this is the first route matched):

But what happens when a non-existent path is entered? For example http://localhost:3000/non-existent:

In a regular JavaScript switch statement, you’ll specify a default clause for this case, right?

In a <Switch> component, this default behavior can be implemented with a <Redirect> component:

This component will navigate to a new location overriding the current one in the history stack:

Now, let’s cover something a little more advanced, nested routes.

Nested routes

A nested route is something like /about/react.

Let’s say that for the messages section, we want to display a list of messages. Each one in the form of a link like /messages/1/messages/2, and so on, that will lead you to a detail page.

You can start by modifying the Messages component to generate links for five sample messages in this way:

This should be displayed in the browser:

To understand how to implement this, you need to know that when a component is rendered by the router, three properties are passed as parameters:

For our purposes, we’ll be using the match parameter.

When there’s a match between the router’s path and the URL location, a `match` object is created with information about the URL and the path. Here are the properties of this object:

  • params. Key/value pairs parsed from the URL corresponding to the parameters.
  • isExacttrue if the entire URL was matched (no trailing characters).
  • path. The path pattern used to match.
  • url. The matched portion of the URL.

This way, in the Messages component, we can destructure the properties object to use the match object:

const Messages = ({ match }) => (
<div>
...
</div>
)

Replace /messages with the matched URL of the match object: 

This way you’re covered if the path ever changes.

And after the message list, declare a <Route component with a parameter to capture the message identifier:

In addition, you can enforce a numerical ID in this way:

<Route path={`${match.url}/:id(\\d+)`} component={Message} />

If there’s a match, the Message component will be rendered. Here’s its definition:

In this component, the ID of the message is displayed. Notice how the ID is extracted from the match.params object using the same name that it’s defined in the path.

If you open the browser, you should see something similar to the following:

But notice that for the initial page of the messages section (/messages) or if you enter in the URL and invalid identifier (like /messages/a), nothing is printed under the list. A message would be nice, don’t you think?

You can add another route for this case, but instead of creating another component to just display a message, we can use the render property of the <Route> component:

<Route
  path={match.url}
  render={() => <h3>Please select a message</h3>
/>

You can define what is rendered by using one of the following properties of <Route>:

  • component. To render a component.
  • render. A function that returns the element or component to be rendered.
  • children. A function that also returns the element or component to be rendered. However, the returned element is rendered regardless of whether the path is matched or not.

Finally, we can wrap the routes in a <Switch> component to guarantee that only one of the two is matched:

However, you have to be careful. If you declare route that renders the message first:

The Message component will never be rendered because a path like /messages/1 will match the path /messages.

If you declare the routes in this order, add exact to avoid this behavior:

Conclusion

In a few words, a router keeps your application UI and the URL in sync.

React Router is the most popular router library for React, and since version 4, React Router declarative defines routes with components, in the same style than React.

In this post, you have learned how to set up React Router, its most important components, how routes work, and how to build dynamic nested routes with path parameters.

But there’s still a lot of more to learn. For example, there a <NavLink>component that is a special version of the <Link> component that adds the properties activeClassName and activeStyle to give you styling options when the link matches the location URL.

The official documentation covers some basic as well as more advanced, interactive examples. Also, don’t forget to check out the advanced React Router concepts post, here on LogRocket’s blog.


Plug: LogRocket, a DVR for web apps

LogRocket is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single page apps.

Esteban Herrera Family man, #Java and #Javascript developer. #Swift, and #VR/#AR hobbyist. Like #books, #movies and still trying many things. eherrera.net

One Reply to “React Router DOM: set-up, essential components, & parameterized routes”

Leave a Reply