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.
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!
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.
Another way apps can make use of routes in a practical manner is by highlighting the currently active page in the menu.
Home - This link takes the user to the main page /
.
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.
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.
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.
function getClassName({isActive}) {
if (isActive) {
return "active";
} else {
return "";
}
}
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.
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 😀
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!
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.
/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>;
}
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 💗
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.
Typically, it's when you need to redirect users programmatically based on certain logic, like the result of a fetch()
operation.
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!
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.
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.
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.
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!