DEV Community

SimoneVeitch
SimoneVeitch

Posted on

Navigating Client-Side Routing

Hello readers,

I'm Simone, currently enrolled in the Academi Xi Front-End Web Development: Transform course. I'm in the midst of Phase 2, which focuses on React.

After completing Phase 1, which laid the foundation for Javascript, Phase 2 introduced me to the world of React.

It took some adjustment moving from a vanilla Javascript mindset, to suddenly having to think in components, states and side effects. The biggest challenge I faced, however, was client-side routing.

I’ll tell you why.

Prior to being introduced to client-side routing the two main concepts that I was introduced to were useState and useEffect. While it took some time to get my head around these two hooks, they are more straightforward in their use.

Client-side routing introduced multiple concepts that I had to get my head around in a very brief amount of time. You need to understand all the components that make up client-side routing to get it to work on your web application and be able to troubleshoot issues that you will no doubt face when trying this out for the first time.

But first things first, why is client-side routing important? Client-side routing enables smooth navigation between different pages on your web application without making a full request to the server. This is beneficial as it leads to a faster user experience when navigating between pages.

In vanilla Javascript you will typically need to click on a link that points to a different HTML file, ie, loading an entirely new HTML document. To do this the browser has to send a request to the server for the new page, which can lead to a less smooth user experience.

Alternatively, to avoid a full page reload using vanilla Javascript, you can use Javascript to manipulate the DOM and update the content, but as it requires manual handling of the DOM it is more error prone.

While I personally find using vanilla Javascript for page navigation more simple and approachable as an aspiring front-end web developer, the benefits of using React and client-side routing outweigh those of vanilla JavaScript (if, and only if, you are building an application where page navigation is necessary).

React uses a single-page application approach, and handles routing within this, which allows for a more seamless navigation. This approach avoids full page reloads, and navigation is instead managed by React components.

Below I will take you through some key concepts relating to client-side routing with code examples from my Phase 2 React web application project.

Getting started

To get started using client-side routing you will need to install React Router, which is a library that handles routing in React applications. Without this you won’t have the components and hooks required to manage navigation and URL changes.

In your terminal add the following to install React Router

npm install react-router-dom
Enter fullscreen mode Exit fullscreen mode

Then, import it into your application in the component where you need to enable client-side routing.


import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
Enter fullscreen mode Exit fullscreen mode

Let me break this down before we continue.

BrowserRouter is the top level component that enables routing. It uses the HTML5 history API to manage the browser’s history stack, which is what allows you to navigate through your app using the browser's back and forward buttons.

Next, you need to wrap your components and routes in <Router> to be able to define routes and navigate between them without refreshing the page. You typically just need to do this in your top-level Router component, which in my case is index.js. You only need to do this once, as the <Router> component provides routing context to all its child components.

import React from "react";
import ReactDOM from "react-dom";
import { BrowserRouter as Router } from "react-router-dom";
import App from "./Components/App";
import './index.css';


ReactDOM.render(
<Router>
  <App />
</Router>,
document.getElementById("root")
);

Enter fullscreen mode Exit fullscreen mode

Now that we have that set up, we can start working on our routing context.

In my web application I have two main components that work with navigation, App and NavBar.

Starting with my App component, I import all the components that I want to navigate to.

import NavBar from "./NavBar";
import DirectoryList from "./DirectoryList";
import ResultsList from "./ResultsList";
import Contact from "./Contact";
import Tips from "./Tips";
import Home from "./Home";
import Footer from "./Footer";
Enter fullscreen mode Exit fullscreen mode

I then wrap all my routes in <Switch>. The purpose of switch is that it ensures that only the first matching <Route>is rendered. This prevents multiple components from rendering at the same time, which is important to ensure a smooth user experience.

Next I wrap each component that will sit on its own page, ie, each component that I want the user to be able to navigate to, as though it’s its own page in <Route>. <Route> defines the paths and the components to render for those paths. For example, when the URL in my code is /directorylist, the DirectoryList component is rendered.

return (
     <div className="app-container">
       <NavBar />
       <Switch>
         <Route exact path="/directorylist">
           <DirectoryList list={list}/>
         </Route>
         <Route exact path="/tips">
           <Tips />
         </Route>
         <Route exact path="/contact">
           <Contact onAddOrganisation={handleAddOrganisation}/>
         </Route>
         <Route exact path="/">
           <Home />
         </Route>
         <Route
           path="/results/:category"
           render={({ match }) => (
             <ResultsList category={match.params.category} />
           )}
         />
       </Switch>
       <Footer />
     </div>
 );
Enter fullscreen mode Exit fullscreen mode

There are a few props that make <Route> work as it does. You can see them all in use here:

<Route exact path="/directorylist">
           <DirectoryList list={list}/>
         </Route>
Enter fullscreen mode Exit fullscreen mode

Path is the URL path to match, ‘exact’ ensures that the route matches exactly, preventing partial matches. This is especially important if you have a path that is “/” like I do for the Home component. And then there is the component to render, when the path matches, which in the above example is my DirectoryList component.

That concludes the routing context in my App component. In summary, the <Router> set up manages the routing context and determines which components to render based on the current URL.

This isn’t where the story ends though, next you need to set up the UI elements for navigating between the different routes, without that your users won’t be able to actually navigate to the different components. In my case, I manage this in my NavBar component.

Navigation interface

To start off, I again first need to import the component that enables rendering.

If you don’t plan to do any styling based on active routes you can import <Link>, but as I am, I imported <NavLink> instead. Both components render an anchor tag (<a>) and update the URL without causing a page reload. The difference is that with <Navlink> you can define styling for the active route.

import { NavLink } from "react-router-dom";
Enter fullscreen mode Exit fullscreen mode

Next, I add all the NavLinks that will appear in my navigation bar.


 return (
   <div className={`navbar ${navBackground ? "navbar-scrolled" : ""}`}>
       <button className={`hamburger ${menuOpen ? "open" : ""}`} onClick={toggleMenu}>
               <div className="line"></div>
               <div className="line"></div>
               <div className="line"></div>
           </button>
           <div className={`nav-links ${menuOpen ? "open" : ""}`}>
     <NavLink to="/" exact className="navlink" activeClassName="active">
       Home
     </NavLink>
     <NavLink to="/directorylist" exact className="navlink" activeClassName="active">
       Directory
     </NavLink>
     <NavLink to="/tips" exact className="navlink" activeClassName="active">
       Tips
     </NavLink>
     <NavLink to="/contact" exact className="navlink" activeClassName="active">
       Contact
     </NavLink>
   </div>
   </div>
 );
}


export default NavBar;
Enter fullscreen mode Exit fullscreen mode

To break down the elements in the code above, the NavBar component provides a user interface for navigation, which is imported into my App components that renders that component. The NavLinks are used to create navigable links that allow users to switch between different routes, and it also enables me to add styling to the link when it matches the current URL.

I can then define the active style in my CSS, providing a better user experience as the user will know which route is active.

The screenshot below is from my web application, and when a route is active, ie, is the page the user is currently on, the nav element has an underline.

Image description

And that is that.

Final words

In summary, I need both the Router setup in my App component for routing logic and the NavLink setup in my NavBar component for navigation UI, to ensure that users can interact with my app and navigate between different pages smoothly.

These are the most fundamental parts of client-side routing. There are other hooks that are important, like useParams and useLocation, but I won’t go in to those here.

Thanks for reading.

Top comments (0)