Routing in React

Harshit Bansal's photo
·

11 min read

Routing in React

What is Routing in React ?

React Routing is the process of managing navigation and rendering components based on the URL in a React application. It allows Single Page Applications (SPAs) to display different pages or views dynamically without reloading the entire page.

Benefits of Routing

  • Improved Performance: Navigations don’t reload the entire page.

  • User-Friendly URLs: Clean and meaningful paths for better usability.

  • Scalability: Suitable for small to complex applications.

  • SEO Optimization: React Router supports server-side rendering for better SEO.

Types of Routing

  1. Server-Side Routing:

    • Requests are sent to the server, which responds with the appropriate HTML page.

    • This method involves reloading the page for each route change.

    • Example: Traditional websites like PHP-based apps.

  2. Client-Side Routing:

    • The routing is handled within the browser, typically for Single Page Applications (SPAs).

    • The page does not reload; instead, the view is updated dynamically.

    • Example: React Router in React applications.

We will use a npm package react-router to handle routing in our React apps.

npm i react-router-dom

Features of React Router

  • Declarative Routing: React Router uses the Routes and Route components to define routes declaratively, making the routing configuration simple and easy to read.

  • Nested Routes: It supports nested routes, allowing for complex and hierarchical routing structures, which helps in organizing the application better.

  • Programmatic Navigation: The useNavigate hook enables programmatic navigation, allowing developers to navigate between routes based on certain conditions or user actions.

  • Route Parameters: It provides dynamic routing with route parameters, enabling the creation of routes that can match multiple URL patterns.


Setting Up React Router

Configure BrowserRouter

BrowserRouter is a component provided by the React Router library. It is used to enable client-side routing in a React application. It works by utilizing the HTML5 History API to keep the UI in sync with the URL in the browser.

Key Features of BrowserRouter

  • It uses clean URLs (e.g., /about) without hash symbols (unlike HashRouter which uses #/about).

  • Changes in the URL do not trigger a full-page reload, allowing seamless navigation.

  • BrowserRouter wraps the entire React application and provides routing context to all its child components.

  • Enables dynamic updates to the browser history stack for navigation (forward/back buttons) without refreshing the page.

How to use BrowserRouter

To configure React router, navigate to the main.jsx file, which is the root file, and import BrowserRouter from the react-router-dom package that we installed, wrapping it around our App component as follows:

// main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
      <BrowserRouter>
         <App />
      </BrowserRouter>
);

We have now successfully installed and imported React router into our app, the next step is to use React router to implement routing. The first step is configuring all of our routes (all the pages/components we want to navigate).


Configure Routes

Routes are configured by rendering <Routes> and <Route> that couple URL segments to UI elements.

Let’s Start with an easy example by configuring Routing between 3 pages which are Home, About Us and Contact Us pages. Once those pages are created, we can now set up and configure our routes in the App.jsx file, which serves as the foundation for our React application:

// App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './Pages/Home';
import About from './Pages/About';
import Contact from './Pages/Contact';

const App = () => {
   return (
      <>
         <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/contact" element={<Contact />} />
            <Route path="/about" element={<About />} />
         </Routes>
      </>
   );
};

export default App;

In the above code that we imported Routes and Route components from react-router-dom and then used them to declare the routes we want.

All Routes are wrapped in the Routes tag, and these Routes have two major properties:

  • path: As the name implies, this identifies the path we want users to take to reach the set component. When we set the path to /about, for example, when the user adds /about to the URL link, it navigates to the component which is set in the element field for that route.

  • element: This contains the component that we want the set path to load. This is simple to understand, but remember to import any components we are using here, or else an error will occur.

No Routes Found

When routing, a situation may cause a user to access an unconfigured route or a route that does not exist. When this occurs, React does not display anything on the screen. This can be fixed by configuring a new route to return a specific component when a user navigates to an unconfigured route as follows:

// App.jsx
import { Routes, Route } from 'react-router-dom';
import NoMatch from './Components/NoMatch';

const App = () => {
   return (
      <>
         <Routes>
            // ... <!-- Other Routes -->
            <Route path="*" element={<NoMatch />} /> <!-- Undefined Routes -->
         </Routes>
      </>
   );
};

export default App;

Using * as path tells the router to render the <NoMatch /> element for all the unconfigured paths.

Nested Routes

Nested Routes in React Router allow you to structure your application’s routing hierarchy by embedding routes within other routes. This approach is ideal for creating layouts or pages with child components, such as dashboards with multiple sections.

<Routes>
  <Route path="dashboard" element={<Dashboard />}>
    <Route index element={<Home />} />
    <Route path="settings" element={<Settings />} />
  </Route>
</Routes>

The path of the parent is automatically included in the child, so this config creates both "/dashboard" and "/dashboard/settings" URLs.

Note: The index attribute in the above code for the <Home /> component means that it’ll be considered as the default component for the parent’s layout path. Index routes can't have children. If you're expecting that behavior, you probably want a layout route.

How are Child Routes rendered ?

Nested routes render components within parent components using an <Outlet/> component. This maintains the context of the parent while rendering child routes.

import { Outlet } from "react-router";

export default function Dashboard() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Outlet /> <!-- Will either be <Home/> or <Settings/> -->
    </div>
  );
}

The <Outlet /> component gets replaced by the respective component whose URL has been called.

Layout Routes

These are similar to the nested routes but they do not have a path for their children because of which they don’t add any segments to the children element’s URL.

<Routes>
  <Route element={<MarketingLayout />}>
    <Route index element={<MarketingHome />} />
    <Route path="contact" element={<Contact />} />
  </Route>

  <Route path="projects">
    <Route index element={<ProjectsHome />} />
    <Route element={<ProjectsLayout />}>
      <Route path=":pid" element={<Project />} />
      <Route path=":pid/edit" element={<EditProject />} />
    </Route>
  </Route>
</Routes>

Here these layouts works as the parent component for other components but do not have their own path.


Dynamic Routing

Dynamic Routing in React refers to creating routes that can change based on variables or parameters in the URL. This is especially useful when dealing with pages or components that display content specific to a user, product, or category. For example, instead of hardcoding individual routes, dynamic routing allows paths like /user/:id to render different content based on the id.

Dynamic routes use parameters prefixed with a colon (:)

<Route path="/user/:id" element={<User />} />

Here, :id is a placeholder for the dynamic value in the URL.

Accessing Parameters

The useParams hook from react-router-dom is used to access the dynamic parts of the URL in your component.

import { useParams } from 'react-router-dom';

function User() {
    const { id } = useParams();
    return <h1>User ID: {id}</h1>;
}

There can be multiple dynamic segments in one route path.

<Route
  path="/c/:categoryId/p/:productId"
  element={<Product />}
/>
import { useParams } from "react-router";

export default function Team() {
  let { categoryId, productId } = useParams();
  // ...
}

Optional Segments

Optional Segments in routing allow you to define parts of a route path that are not mandatory. You can make a route segment optional by adding a ? to the end of the segment. Optional Segments can be both static and dynamic.

//Dynamic
<Route path=":lang?/categories" element={<Categories />} />

//Static
<Route path="users/:userId/edit?" component={<User />} />

Linking

Linking refers to navigating between different routes in a React application using the <Link> component. It provides client-side navigation, meaning the page doesn’t refresh when moving between routes, improving performance and providing a smooth user experience.

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

function Page() {
  return (
    <div>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
      <Link to="/contact">Contact</Link>
    </div>
  );
}

Here, to has to be provided with the path to the component.

Note: replace attribute is used to replace the current entry in the browser history instead of adding a new one.

The NavLink component in React Router is a special type of <Link> that helps create navigation links with styling or behavior based on whether the link matches the current URL. It is commonly used for building menus or navigation bars where the active route should be visually distinct from others.

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

function Navbar() {
  return (
    <nav>
      <NavLink to="/" end>Home</NavLink>
      <NavLink to="/about">About</NavLink>
      <NavLink to="/services">Services</NavLink>
    </nav>
  );
}

Props:

  1. to: Defines the path to navigate to.

  2. end: Ensures the active state applies only to exact matches.

     <NavLink to="/" end>Home</NavLink>
    
  3. className: Function or string to dynamically set the class name. Whenever a NavLink is active, it will automatically have an .active class name for easy styling with CSS:

     <NavLink to="/about" className={({ isActive }) => isActive ? 'active-class' : ''}>
       About
     </NavLink>
    
  4. style: Function to apply inline styles based on active state.

     <NavLink to="/services" style={({ isActive }) => ({ color: isActive ? 'red' : 'blue' })}>
       Services
     </NavLink>
    

UseNavigate

This hook allows the programmer to navigate the user to a new page without the user interacting.

For normal navigation, it's best to use Link or NavLink. They provide a better default user experience like keyboard events, accessibility labeling, "open in new window", right click context menus, etc. Reserve usage of useNavigate to situations where the user is not interacting but you need to navigate, for example:

  • After a form submission completes

  • Logging them out after inactivity

  • Timed UIs like quizzes, etc.

import { useNavigate } from "react-router";

export function LoginPage() {
  let navigate = useNavigate();

  return (
    <>
      <MyHeader />
      <MyLoginForm
        onSuccess={() => {
          navigate("/dashboard");
        }}
      />
      <MyFooter />
    </>
  );
}

Some more React Router Hooks

  1. useSearchParams

Search params are the values after a ? in the URL. They are accessible from useSearchParams, which returns an instance of URLSearchParams. The URLSearchParams API is used to handle query parameters in the URL, making it easy to retrieve, update, or manipulate these parameters within your React application. This is useful for passing data in URLs, such as search filters or pagination information.

How to use it ?

  1. Accessing Query Parameters

    The useSearchParams hook is a React-friendly wrapper around URLSearchParams. It allows you to read and modify query parameters in the current URL.

     import { useSearchParams } from 'react-router-dom';
    
     function SearchPage() {
       const [searchParams] = useSearchParams();
    
       const query = searchParams.get('query'); // Get the value of "query"
       const page = searchParams.get('page');   // Get the value of "page"
    
       return (
         <div>
           <p>Query: {query}</p>
           <p>Page: {page}</p>
         </div>
       );
     }
    
  2. Updating Query Parameters

    You can update query parameters by modifying the searchParams and setting it using the second value returned by useSearchParams.

     const [searchParams, setSearchParams] = useSearchParams();
    
     const updateParams = () => {
       setSearchParams({ query: 'react', page: 2 });
     };
    
     return <button onClick={updateParams}>Update Search Params</button>;
    

Working with URLSearchParams Outside React Router

The native URLSearchParams API can also be used directly if needed.

const params = new URLSearchParams(window.location.search);

console.log(params.get('query')); // Retrieve a parameter
params.set('sort', 'asc');        // Update a parameter
params.delete('filter');          // Remove a parameter

window.history.pushState({}, '', `${window.location.pathname}?${params}`);
  1. useLocation

    The useLocation hook in React Router is used to access the current location object, which contains information about the current URL in your application. This is helpful when you need to analyze or respond to the URL, such as extracting query parameters, pathname, or state passed during navigation.

import { useLocation } from 'react-router-dom';

const location = useLocation();

Location Object

The location object returned by useLocation has the following properties:

  1. pathname: The current URL path (e.g., /about).

  2. search: The query string part of the URL (e.g., ?user=123).

  3. hash: The hash fragment of the URL (e.g., #section1).

  4. state: Any data passed during navigation using the state prop in navigate or <Link>.

  5. key: A unique identifier for the current history entry (useful for managing state).


Lazy Loading

Lazy loading is a technique in which components that are not required on the home page are not loaded until a user navigates to that page, allowing our application to load faster than having to wait for the entire app to load at once. This contributes to improved performance, which leads to a positive user experience.

To implement lazy loading, simply go to App.jsx and wrap our routes with the Suspensecomponent, along with a fallback props that are rendered on the screen until the component loads:

// App.jsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

import NavBar from './Components/NavBar';
const Home = lazy(() => import('./Pages/Home'));
const About = lazy(() => import('./Pages/About'));
const Products = lazy(() => import('./Pages/Products'));
const ProductDetails = lazy(() => import('./Pages/ProductDetails'));
const NoMatch = lazy(() => import('./Components/NoMatch'));

const App = () => {
   return (
      <>
         <NavBar />
         <Suspense fallback={<div className="container">Loading...</div>}>
            <Routes>
               <Route path="/" element={<Home />} />
               <Route path="/about" element={<About />} />
               <Route path="/products" element={<Products />} />
               <Route path="/products/:slug" element={<ProductDetails />} />
               <Route path="*" element={<NoMatch />} />
            </Routes>
         </Suspense>
      </>
   );
};

export default App;

lazy(() => import(‘./Component.jsx’)) → This type of syntax is used to dynamically imports the component.