DEV Community

guzdaniel
guzdaniel

Posted on

Client-side Routing with React Router v5

Routing

Web applications today use different types of routing to respond to changing URLs and navigation within the application and help with the complexity of implementing web apps. In a typical or traditional web application, when the user clicks a link or enters a URL in the browser, the browser sends a request for a document to the backend server that maps to that current URL, waits for the server to generate the HTML, waits for the request to come back and then displays the new and ready HTML document. The browser also evaluates the necessary CSS and Javascript code. Then, for every new page navigation or link clicked, the process gets repeated and more requests to the server for new HTML documents are made, which requires constant full-page reloads. In other words, we consult the server for every new page we visit.

Client-side Routing

Today, we develop more efficient single-page applications, where the idea is to get everything we need from the server on the initial request. They are implemented with a single HTML document that's usually paired with a JavaScript bundle, so the data necessary to navigate the app would be available locally already and all the pages of your application might render on the first page load. Route transitions like clicking links and navigation within the application feel much quicker since we wouldn't be making additional server requests for new HTML documents or doing full-page reloads. This is the idea behind client-side routing.

As the name suggests, the routing is done on the client side, rather than relying on the server. It uses client-side code to try to emulate traditional routing by swapping a "view" of the application with another "view" depending on the URL it's currently matching on. It will instantly render some new UI and will then render the page dynamically with new content by making data requests to the server using JavaScript APIs like the 'fetch' method.

With client-side routing, JavaScript code is responsible for handling the routing, fetching, and displaying of the content in the browser.

React Router

We use React as a library for building Single-Page Applications. React, however, mainly focuses on building user interfaces so it doesn't really have a built-in mechanism for routing.

In that case, we use a routing library called React Router DOM, which contains the DOM bindings for React Router (the most popular routing library for React) or in other words, the router components to use on web applications.

React Router allows us to use two things:

  1. Conditional rendering of components based on the URL. So when the URL is currently "/albums" then the <Albums /> Component is rendered.

  2. Programmatic navigation with JavaScript. When a link to the Albums page is clicked by the user, the URL changes to "/albums". Then the page content updates to reflect that, but done internally with JavaScript, without making requests to the server.

Conditional Rendering of Components

Some of the components that React Router DOM provides for us to use in our web applications are :

  • A Router : <BrowserRouter />
  • Route Matchers: <Route />, <Switch />

Using React Router Components

To implement routes in our application, we first need to install react-router-dom using npm like so:

$ npm install react-router-dom@5
Enter fullscreen mode Exit fullscreen mode

(Here we are using version 5 of React Router instead of 6, for learning purposes)

After installing, we need to import ReactDOM to render our application.

When looking at our index.js code, we are importing ReactDOM at the beginning of our file to be able to use the ReactDOM.render() method, which will help us render our top level component to the browser. In this case, it is our App component.

import React from "react";
import ReactDOM from "react-dom";

ReactDOM.render(<App />, document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

Then we can head to our App component where we need to enclose the entire application that is being returned using BrowserRouter.

BrowserRouter Component

We use the BrowserRouter component as the base for our routing in our applications. This is where we declare how React Router will be used. It provides React Router context features to our application. For this reason, we wrap our whole application in it where we can later use the Route component within it as many times as we need to to create our routes for our application.

To use BrowserRouter, we also first need to import it at the beginning of our own component.

We can change the name or give BrowserRouter an alias to use in our component. Here we'll simply use the word "Router" to refer to BrowserRouter in our App component.

This looks like so:

import React from "react";
import { BrowserRouter as Router }  from "react-dom";

function App() {

return (
     <Router>
        <div className="App">
          <Albums />
        </div>
     <Router/>
  )
}
Enter fullscreen mode Exit fullscreen mode

Route Component

The Route component can be used inside our Router component to wrap around our own components to create routes between app components and a specific URL in our application.

When the URL matches a specified path in our Route component, it renders its child component or the component that is enclosed inside Route. So this is where conditional rendering happens.

The Route component uses a built-in prop called path to pass in the path name that the URL needs to match with in order to render its child.

To use Route, we also need to import it at the beginning of our component along with BrowserRouter.

Here is an example:

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";

function App() {
  return (
     <Router>
        <div className="App">
          <Route path="/albums">
              <Albums />
          </Route>
        </div>
     <Router/>
  )
}
Enter fullscreen mode Exit fullscreen mode

If we wanted to add additional Routes to our routing logic it could look like this:

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";

function App() {
  return (
     <Router>
        <div className="App">
          <Route path="/">
             <Home>
          </Route>
          <Route path="/albums">    
             <Albums />
          </Route>
        </div>
     <Router/>
  )
}
Enter fullscreen mode Exit fullscreen mode

When seeing how this code gets rendered on the browser, we see this:

Route Example

The <Home /> component is being rendered even though we can clearly see our URL is currently on "/albums". This is because <Route path> is rendering all routes that match the beginning of the current URL, not the whole thing. Since "/albums" contains a slash at the beginning, <Home /> component is rendered along with the <Albums /> component.

Another example of this is: If we used a URL with "/albums/new" in the browser to be able to render an <AlbumForm /> component, all three components (Home, Albums, and AlbumForm) would get rendered because their corresponding routes would all match with "/albums" in the beginning of their paths.

To avoid this unpredictable behavior, we turn to use our Switch component.

Switch Component

When the <Switch> component is rendered, it searches through its children <Route> elements to find one whose path matches the current URL. When it finds the first one, even if it's a partial match, it renders that route and ignores the rest.

So the best practice is to put routes with more specific paths before the less-specific ones. We put the "/" route at the end since all of them include the slash.

If we wanted to match the route exactly to our URL to avoid any partial matches, we could use a special prop called exact. This can be useful for the "/" route, so that it can only render the <Home> component when the URL is exactly "/" and nothing else.

To use Switch, we also need to import it at the beginning of our component along with BrowserRouter and Route.

Here is an example of using the Switch component with best practices:

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

function App() {
  return (
     <Router>
        <div className="App">
          <Switch>
            <Route path="/albums/new">
               <AlbumForm>
            </Route>
            <Route path="/albums">    
               <Albums />
            </Route>
            <Route exact path="/">    
               <Home />
            </Route>
          </Switch>
        </div>
     <Router/>
  )
}
Enter fullscreen mode Exit fullscreen mode

Programmatic Navigation with <Link/> and <NavLink/> Components

So far, users will have to manually change the URL to see different views or trigger certain routes to render. However, React Router also improves the user experience by giving us a way to let users trigger those routes without having them know the paths.

In this sense, React Router uses programmatic navigation by providing us with two components to use in our applications:

  • <Link> : ideal for creating hyperlinks
  • <NavLink>: ideal for creating Navigation Bars

We call <Link> and <NavLink> components route changers because they help us change routes quickly and efficiently.

Using <Link> and <NavLink>

Whenever the <Link> component is rendered, an anchor tag (<a>) will be rendered to the DOM. So when the link is clicked by the user, the URL in the browser changes to the path in the "to" prop of the <Link> component. The routes then get re-rendered, displaying the right component for the route at the new URL. This is done all internally with JavaScript code, without making requests to the server or refreshing the page.

We also have the choice to use a <NavLink>component which works similar to the <Link> component but that knows whether or not it is "active". It is used for creating links that add styling attributes to indicate to the user which route is currently active and which link is currently selected. They are ideal for creating navigation bars in our application.

The <Link> component is used to navigate the different routes on our application, but <NavLink> is also used to add the style attributes to the active routes.

<NavLink> uses a prop called activeClassName that applies a class name whenever the route is active. So it styles the links when they are active. The NavLinks can style themselves as "active" when the path at its "to" prop matches the current URL location.

Here is an example of a NavLink where we pass in the name of a style class "selected" to the activeClassName prop of the NavLink to use for when it becomes active:

<NavLink to="/" activeClassName="selected">Home</NavLink>
Enter fullscreen mode Exit fullscreen mode

We can also use an activeStyle prop that takes in an object (or a function in some cases) and applies a specified style to the NavLink for when it is active. This is useful for applying a style that isn't related to a class, or in other words, inline styling. Here is an example of using inline styling for the NavLink to use for when it is active:

<NavLink to="/" activeStyle={{ color: "white" }}>Home</NavLink>
Enter fullscreen mode Exit fullscreen mode

In order to use <Link> or <NavLink>, we also need to import them at the top of our component.

Here is an example of a component that uses <NavLink> to implement a navigation bar. It gives the basic styling of the NavBar inline by setting the style attribute to an object of styles attributes. It then also adds styling for when the link is active by adding some inline styling object to the activeStyle prop:

import React from "react";
import { NavLink } from "react-router-dom";

const linkStyles = {
    display: "inline-block",
    padding: "1rem 4rem 1rem",
    margin: "0.2rem",
    background: "Black",
    textDecoration: "none",
    color: "lightgreen",
    fontsize: "1.2em"

};


function Navigation() {
    return (
        <div>
            <NavLink
                to="/"
                exact
                style={linkStyles}
                activeStyle={{
                    color: "white",
                }}
            >
                Home
            </NavLink>
            <NavLink
                to="/albums"
                exact
                style={linkStyles}
                activeStyle={{
                    color: "white",
                }}
            >
                My Albums
            </NavLink>
       </div>
     )
}

export default Navigation
Enter fullscreen mode Exit fullscreen mode

"exact"
You can notice that we use the exact prop here for each NavLink to make sure that it knows to only set activeStyle when the route is exactly equal to the link. Otherwise we can have multiple links changing styles.

Summary

With client-side routing, although initial loading time could be a negative factor as the whole application needs to load on the first server request, routing between concise components can provide a big advantage as the amount of data it renders is less on the page. All data is handled locally and so a lot of the rest of the data needed to render can also be handled very quickly by the DOM. The user experience is also better, with more seamless page transitions and navigation that easily happens by just switching between components. It helps keep the feel of a single-page application as well, as the page doesn't need to keep refreshing when it renders a new view.

References:

React Router - Client Side Routing
React Router v5 - Primary Components
React Router v5 - NavLink

Top comments (0)