Skip to main content

🌟 Mastering React Router – Navigation Made Simple

If you’re building a React app, chances are you’ll need navigation. You want users to move between different pages (like Home, About, Dashboard) without reloading the browser. That’s where React Router comes in.

In this blog, we’ll cover everything you need to know about React Router: declarative routing, nested routes, dynamic routing, route guards, and even code splitting for performance.


1. Declarative Routing → BrowserRouter, Routes, Route

Definition:
React Router provides declarative routing, meaning we describe what routes should exist, and React Router handles the navigation for us.

Key Components:

  • BrowserRouter → wraps the app and keeps UI in sync with the browser’s URL.
  • Routes → container for route definitions.
  • Route → defines which component to render for a specific path.

Example:

import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} /> 
        <Route path="/about" element={<About />} /> 
      </Routes>
    </BrowserRouter>
  );
}

👉 Here, when you visit /, Home is rendered. When you visit /about, About is rendered.


2. Nested Routes → The Outlet Pattern

Definition:
Nested routes allow us to render child routes inside parent routes. This is useful for dashboards, layouts, or multi-step forms.

Explanation:
React Router provides the Outlet component to show nested content.

Example:

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

function DashboardLayout() {
  return (
    <div>
      <h2>Dashboard</h2>
      <nav>
        <Link to="profile">Profile</Link> | 
        <Link to="settings">Settings</Link>
      </nav>
      <Outlet /> {/* Child routes appear here */}
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/dashboard" element={<DashboardLayout />}>
          <Route path="profile" element={<h3>Profile Page</h3>} />
          <Route path="settings" element={<h3>Settings Page</h3>} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

👉 Visiting /dashboard/profile renders the DashboardLayout plus the Profile page inside the Outlet.


3. Dynamic Routing → useParams, useNavigate

Definition:
Dynamic routes let us create paths that depend on parameters (like /user/1, /user/2).

useParams: Reads URL parameters.
useNavigate: Programmatically navigates to a route.

Example:

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

function User() {
  const { id } = useParams();
  const navigate = useNavigate();

  return (
    <div>
      <h2>User ID: {id}</h2>
      <button onClick={() => navigate("/")}>Go Home</button>
    </div>
  );
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/user/:id" element={<User />} />
      </Routes>
    </BrowserRouter>
  );
}

👉 Visiting /user/42 will render User ID: 42.


4. Route Guards & Redirects → Authentication Checks

Definition:
Sometimes, we need to restrict access to certain routes (like /dashboard) unless the user is logged in. This is called a route guard.

Explanation:
We can build a PrivateRoute component that checks authentication.

Example:

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

function PrivateRoute({ children }) {
  const isAuthenticated = false; // Imagine checking user auth here
  return isAuthenticated ? children : <Navigate to="/login" />;
}

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/dashboard" element={<PrivateRoute><h2>Dashboard</h2></PrivateRoute>} />
        <Route path="/login" element={<h2>Login Page</h2>} />
      </Routes>
    </BrowserRouter>
  );
}

👉 If isAuthenticated is false, users are redirected to /login.


5. Code Splitting with Lazy Routes → React.lazy + Suspense

Definition:
Instead of loading all components at once, we can lazy-load them when needed. This improves performance, especially for large apps.

Explanation:
React provides React.lazy and Suspense to load components on demand.

Example:

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

const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<h2>Loading...</h2>}>
        <Routes>
          <Route path="/" element={<Home />} /> 
          <Route path="/about" element={<About />} /> 
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

👉 Now, About is loaded only when the user visits /about.


🎯 Final Thoughts

  • Declarative Routing: Define routes with BrowserRouter, Routes, Route.
  • Nested Routes: Use Outlet for layouts and dashboards.
  • Dynamic Routing: Access params with useParams, navigate with useNavigate.
  • Route Guards: Protect routes with custom components and redirects.
  • Code Splitting: Optimize performance with React.lazy and Suspense.

🚀 With these patterns, you can build scalable, secure, and performant navigation in React apps.


BrowserRouter → Routes → Outlet → Component

Comments

Popular posts from this blog

🌟 Dot net Microservices interview questions

Here is a comprehensive list of 200 .NET microservices coding questions covering all core microservices concepts and cross-cutting concerns relevant for designing, building, deploying, and maintaining .NET-based distributed systems. 🧩 A. Microservices Fundamentals (20) Build a microservice in .NET 8 that exposes a simple CRUD API. Implement communication between two microservices using REST. How would you design microservices for an e-commerce application? Create a microservice that handles user registration and login. How do you isolate domain logic in a microservice? How to apply the "Single Responsibility Principle" in microservices? Design a service registry/discovery mechanism using custom middleware. Implement a service that handles file uploads and metadata separately. Build a stateless microservice and explain its benefits. Implement health check endpoints in .NET 8. Demonstrate versioning in a microservice API. Add Swagger/OpenAPI support to your m...

⚡ Part 1: Introduction to Generics in C#

🌍 Why Do We Need Generics? Imagine you want to create a stack (like a pile of books 📚): You can push items on top You can pop items off the top If we write a stack for integers : public class IntStack { private int[] items = new int[10]; private int index = 0; public void Push(int item) => items[index++] = item; public int Pop() => items[--index]; } 👉 Problem: This only works for int . What if we want a string stack ? Or a Customer stack ? We’d have to duplicate code for every type. 😢 ✅ Solution: Generics Generics let us create type-safe reusable code without duplication. We can say: “I don’t care what type it is yet — I’ll decide later.” 1) Generic Classes Here’s a generic stack : // Generic class "Stack<T>" // The <T> is a placeholder for any type public class Stack<T> { private T[] items = new T[10]; // Array of type T private int index = 0; // Push adds an item of type T public void P...

🚪 Part 9: API Gateway for .NET 8 Microservices (Ocelot & YARP)

Once you have multiple microservices (Products, Orders, Payments…), exposing each one directly to clients gets messy: Different base URLs Duplicated auth logic No unified rate limiting / caching Hard to evolve routes or aggregate data 👉 Enter the API Gateway — your single front door for all microservices. An API Gateway handles: ✅ Routing & path rewriting ✅ Load balancing, retries, circuit breakers ✅ Authentication & Authorization (JWT, OAuth2) ✅ Rate limiting & caching ✅ Aggregation (compose results from multiple services) In this post we’ll implement two strong options: Ocelot → config-driven, mature, DevOps-friendly YARP (Yet Another Reverse Proxy) → Microsoft’s code-first, extensible gateway ⚖️ Ocelot vs YARP — When to Choose Ocelot → JSON config, minimal C#, built-in QoS (rate limit, circuit breaker). Perfect for teams that like DevOps config-as-code. YARP → full C# control, middleware-friendly, can embed into broader apps (e.g. add dashb...