paint-brush
Advanced React Router V6 Techniques: Going Beyond the Basicsby@sojinsamuel
6,354 reads
6,354 reads

Advanced React Router V6 Techniques: Going Beyond the Basics

by Sojin SamuelJuly 2nd, 2023
Read on Terminal Reader
Read this story w/o Javascript

Too Long; Didn't Read

React Router is an amazing tool that enables us to create dynamic and smooth navigation in our React applications. There are some lesser-known techniques that can elevate your routing abilities to a whole new level. We'll cover: Handling the notorious 404 page. Creating an active page indicator to highlight the current route. Utilizing `useNavigate` and `useLocation` hooks.
featured image - Advanced React Router V6 Techniques: Going Beyond the Basics
Sojin Samuel HackerNoon profile picture

I'm really excited to share some advanced React Router techniques with you today. React Router is an amazing tool that enables us to create dynamic and smooth navigation in our React applications.


While it is commonly used for basic routing needs, there are some lesser-known techniques that can elevate your routing abilities to a whole new level.


Today, I'll be sharing a few of my favourites that I've discovered over time.


We'll cover:

  1. Handling the notorious 404 page.
  2. Creating an active page indicator to highlight the current route.
  3. Utilizing useNavigate and useLocation hooks.


By the time you finish reading this article, you'll have a strong grasp of these advanced React Router techniques and feel confident applying them in your own projects.


So, let's dive right in and enhance our routing skills together!


Are you ready? Let's get started!

Dealing with a 404 page


When working on bigger projects, it's essential to take care of routes that can't be found. We often refer to these routes as 404 routes because they return the HTTP status code 404 when a document is not found on the server.


By the way, if you're a cat lover, you might enjoy checking out http.cat.


Now, let's dive into creating a special Route that will be triggered whenever none of the other routes match the current URL in the browser.

import {BrowserRouter, Routes, Route} from "react-router-dom";

function App() {
    return <BrowserRouter>
        <Routes>
            <Route path="/" element={<p>Landing page</p>}></Route>
            <Route path="/products" element={<p>Products page</p>}></Route>
            <Route path="*" element={<p>404 page</p>}></Route>
        </Routes>
    </BrowserRouter>
}

When accessing the primary page /, users will be directed to the Landing page. Navigating to the /products page will lead them to the Products page. However, if any other links are accessed that do not correspond to a specific route, the 404 page will be displayed.


The reason behind this behavior is the implementation of a specialized <Route> for the 404 page, where the path property is set to *. This configuration ensures that React Router will exclusively utilize this route when no other routes can be matched. The specific placement of the 404 Route is inconsequential and can be positioned anywhere within the <Routes>...</Routes> section, as an integral component of it.

Active page

Another way apps can make use of routes in a practical manner is by highlighting the currently active page in the menu.


Let's imagine you have a navigation menu with two links:


  1. Home - This link takes the user to the main page /.

  2. About - This link takes the user to the about page /about.


With React Router, it becomes possible to automatically highlight the About link when the user is on the /about route. Similarly, you can highlight the Home link when the user is on the / route. The best part is, you can achieve this without the need for complex conditionals.


Here's a simple way to make it work:

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

function getClassName({isActive}) {
    if (isActive) {
        return "active"; // CSS class
    }
}

function App() {
    return <ul>
        <li>
            <NavLink to="/" className={getClassName}>Home</NavLink>
        </li>
        <li>
            <NavLink to="/about" className={getClassName}>About</NavLink>
        </li>
    </ul>
}


The code you see above needs some CSS to make the active class work. So, we can enhance it by making the menu link of the currently active page appear bold and stand out.

.active {
    font-weight: bold;
}


TheclassName attribute of the NavLink element has the flexibility to accept a function instead of just a string. When you decide to use a function, it will be given an object as a parameter, and this object will have the isActive property already set on it. To easily access this property within the function, we use destructuring by including {isActive} in the parameter.


Next, we proceed to verify if isActive holds a truthy value. If that's the case, we will return the CSS class name active.


When the current path in React Router matches the to attribute of the NavLink component, the isActive function will evaluate to a boolean value.


As a result, React Router will seamlessly include the active class in the corresponding <NavLink /> element, indicating it as the currently active route.

Shorter version

If you prefer a shorter approach, you can use the ternary operator to refactor the code mentioned above. This operator provides a way to replace an if/else statement by using the format: condition ? truthy expression : falsy expression.

I don't really use the ternary operator much because it can often make the code less readable. However, it’s acceptable to use in this case.


Consider the following as an example:

function getClassName({isActive}) {
    if (isActive) {
        return "active";
    } else {
        return "";
    }
}


You can substitute the above code with the following approach:

function getClassName({isActive}) {
    return isActive ? "active" : "";
}


You simply need to ensure that the return statement is placed outside of the ternary operator. When the isActive condition is true, the expression after ? will be executed "active". Otherwise, the expression after : will be executed "".


Now, we can eliminate the need for a separate function definition called getClassName. Instead, we can use an arrow function like this: ({isActive}) => isActive ? "active" : "".


If you find this change too difficult to read, you can continue using a separate function as before.

The final code would look like this:

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

function App() {
    return <ul>
        <li>
            <NavLink to="/" className={({isActive}) => isActive ? "active" : ""}>Home</NavLink>
        </li>
        <li>
            <NavLink to="/about" className={({isActive}) => isActive ? "active" : ""}>About</NavLink>
        </li>
    </ul>
}

Oh boy, I can practically hear your thoughts!

Sojin, you've gone and broken the DRY principle, haven't you?

This is just an example. No need to get all worked up 😀

Choosing Between Link and NavLink: What Sets Them Apart?

You might have already noticed that I'm using NavLink instead of our usual friend, Link. But don't worry, let me explain why. You see, the <Link /> component doesn't allow you to use a function definition for the className prop. Yeah, it might be surprising, but that's just how it is when you're working with <Link />.


One common mistake I've seen is junior React developers trying to use function definitions in the className prop of a <Link /> component, which simply doesn't work.


If you're interested, I can write an article about this topic. Just let me know in the comments!

useNavigate

In certain circumstances, you might need to programmatically relocate pages within your React application. For example, to redirect the visitor to a specific page, you can submit a request and wait for the response.


Here are a few examples:

  • If the user isn't signed in, send them to the login page.
  • After the getLogin request has successfully completed, redirect the user to the /dashboard.


This requires using the useNavigate() hook, which returns a function that you can use to programmatically redirect the user to a new route.


Take a look:

import React, {useEffect} from "react";
import {useNavigate} from "react-router-dom";

function HelpPage() {
    const navigate = useNavigate();

    useEffect(() => {
        const isLoggedIn = window.localStorage.getItem("isLoggedIn");
        if (!isLoggedIn) {
            navigate("/login");
        }
    }, []);

    return <h2>Help page</h2>;
}

Using BrowserRouter is Essential!

In this example, we're checking if the localStorage has a value assigned to the key isLoggedIn. If it doesn't, we'll redirect the user to the /login page using the navigate("/login") function.


To make it work, just remember to use the useNavigate() hook within a component that's nested inside the <BrowserRouter /> component. Otherwise, it won't function properly.


For instance, the following example won't work:

import React from "react";
import {BrowserRouter, useNavigate} from "react-router-dom";

function App() {
    // ❌ This breaks because the component was not wrapped by BrowserRouter, but its children are.
    const history = useNavigate();

    return <BrowserRouter>
        {/* ... */}
    </BrowserRouter>;
}

If you're trying to use the useNavigate() hook inside the App() component, you might run into a little hiccup. That's because it won't work unless you wrap the App() component with <BrowserRouter />.


But don't worry!


The good news is that all the child components inside App will automatically be wrapped by <BrowserRouter />, so you can happily use useNavigate() in those child components without any issues.


However, if you really need to use useNavigate() in the <App /> component itself, there's a simple solution. You just need to create a parent component that wraps <App /> and make sure to move the <BrowserRouter /> inside that parent component. Once you do that, you'll be good to go and can use useNavigate() in the <App /> component to your heart's content 💗

Ensuring Accessibility and Proper Link Usage

When it comes to making your website accessible, it's important to pay attention to how your links are structured. To ensure that, it's recommended to use <Link /> elements instead of other methods.


Why?


Well, these elements will automatically render as <a> tags, which are known for their accessibility features.

Now, here's a friendly tip: avoid replacing your normal <Link /> elements with the .push() method provided by useNavigate(). This method should only be utilized in cases where using a <Link /> element isn't possible.


When does that happen?

Typically, it's when you need to redirect users programmatically based on certain logic, like the result of a fetch() operation.

useLocation

What if you want to execute a piece of code every time React Router takes you to a new address?

You can accomplish that easily with the help of the useLocation hook. But before we dive into the details, let's explore some scenarios where this can come in handy. One of the most common use cases is sending a pageview event to your analytics service, like Google Analytics.


However, it's worth mentioning that Google Analytics 4 now takes care of this automatically, so you don't necessarily have to implement it yourself.


Nevertheless, learning about the useLocation hook is still valuable, as you might find other purposes for it. For instance, you can use it to integrate with other libraries and analytics services, or to execute specific actions based on the visited routes.


The possibilities are endless!

useLocation hook

The useLocation hook is super handy because it gives you information about the route that's currently being used.


Here's a friendly guide on how to use the useLocation hook:

import {BrowserRouter, Routes, Route, useLocation} from "react-router-dom";
import {createRoot} from "react-dom/client";

function App() {
    const location = useLocation();
    console.log(location.pathname);

    return <Routes>
        {/* routes here... */}
    </Routes>
}

createRoot(document.querySelector("#react-root")).render(<BrowserRouter><App /></BrowserRouter>);


Now, you can access the route information through the location variable. It's an object that contains the current pathname.


To find out which route the user is currently browsing, simply use location.pathname. It will give you something like /, /about, or any other route you've set up.


Remember, just like with the useNavigate hook, it's important to have your code running inside a component that's wrapped by <BrowserRouter />. Otherwise, the useLocation hook won't do its magic.

Responding to Location Changes

When the location changes, you can make use of the useLocation hook to access the current URL path in React Router. By wrapping your code with a useEffect hook and setting the dependency to location, you can run a specific piece of code whenever there is a navigation to a new URL.


Here's an example of how to do it:

import React, {useEffect} from "react";
import {BrowserRouter, Routes, Route, useLocation} from "react-router-dom";
import {createRoot} from "react-dom/client";

function App() {
    const location = useLocation();

    // run a piece of code on location change
    useEffect(() => {
        console.log(location.pathname);
        // send it to analytic, or do some conditional logic here
    }, [location]);

    return <Routes>
        {/* routes here... */}
    </Routes>
}

createRoot(document.querySelector("#react-root")).render(<BrowserRouter><App /></BrowserRouter>);

Whenever the user moves to a new route during navigation, the code inside the useEffect hook will run and do its thing. It's basically a way to make sure that specific code gets executed whenever there's a change in the location object.

Nice Job, Now your a React Router Expert 🥳

To wrap it up, this article has given you a deeper understanding of React Router. While you might not use all of the features discussed here. Still it's fantastic that you’re aware of them. If you ever find yourself needing to incorporate similar functionality in the future, you know exactly where to look. So, be sure to save or bookmark this post for easy reference. And remember, if you have any questions or need further assistance, feel free to reach out to me on Twitter. I'm here to help!