DEV Community

Cover image for React Router Setup: Complete Guide for React Applications
Md. Maruf Rahman
Md. Maruf Rahman

Posted on • Originally published at marufrahman.live

React Router Setup: Complete Guide for React Applications

When I first started building React applications, I thought routing would be simple. Just show different components based on the URL, right? Then I had to implement protected routes, nested layouts, route parameters, and handle 404 pages. That's when I realized that proper routing setup is crucial for building maintainable React applications.

React Router DOM is the de facto standard for routing in React applications. It enables client-side routing, which means navigation happens without full page refreshesβ€”your app feels fast and responsive. But more importantly, it provides a declarative way to define your application's routes, handle navigation, and protect routes based on authentication status.

πŸ“– Want the complete guide with more examples and advanced patterns? Check out the full article on my blog for an in-depth tutorial with additional code examples, troubleshooting tips, and real-world use cases.

What is React Router?

React Router DOM is a routing library for React that provides:

  • Client-side routing - Navigation without page refreshes
  • Declarative routes - Define routes using JSX
  • Nested routes - Organize routes hierarchically
  • Protected routes - Control access based on authentication
  • Route parameters - Dynamic routes like /products/:id
  • Query parameters - Handle URL search params
  • Programmatic navigation - Navigate from code

Installation

Install React Router DOM:

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

For TypeScript projects, types are included automatically.

Basic Router Setup

Setting up the router in your main app:

import { BrowserRouter, Routes, Route } from "react-router-dom";
import { Provider } from "react-redux";
import { store } from "./state/store";
import Login from "./pages/Auth/Login";
import Signup from "./pages/Auth/Signup";
import DashboardLayout from "./components/DashboardLayout";
import Home from "./pages/Home";
import Products from "./pages/Products/Products";
import AddProduct from "./pages/Products/AddProduct";
import EditProduct from "./pages/Products/EditProduct";

function App() {
  return (
    <Provider store={store}>
      <BrowserRouter>
        <Routes>
          {/* Public routes */}
          <Route path="/login" element={<Login />} />
          <Route path="/signup" element={<Signup />} />

          {/* Protected routes with layout */}
          <Route element={<DashboardLayout />}>
            <Route path="/" element={<Home />} />
            <Route path="/products" element={<Products />} />
            <Route path="/products/add" element={<AddProduct />} />
            <Route path="/products/edit/:id" element={<EditProduct />} />
          </Route>

          {/* 404 route */}
          <Route path="*" element={<div>404 - Page Not Found</div>} />
        </Routes>
      </BrowserRouter>
    </Provider>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Key Components

  • BrowserRouter - Wraps your app and enables routing
  • Routes - Container for all route definitions
  • Route - Defines a single route with path and element
  • Outlet - Renders nested routes (used in layout components)

Using Outlet for Nested Routes

Creating a layout component with Outlet for nested routes:

import { Outlet, useNavigate } from "react-router-dom";
import SideBar from "./SideBar";
import MainContent from "./MainContent";

export default function DashboardLayout() {
  const [pageTitle, setPageTitle] = useState("");
  const navigate = useNavigate();

  const context = {
    setPageTitle,
  };

  return (
    <div className="min-h-screen bg-gray-100">
      <div className="flex">
        <SideBar />
        <MainContent>
          <Outlet context={context} />
        </MainContent>
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The Outlet component renders the matched child route. This allows you to create shared layouts (like sidebars and headers) that wrap multiple routes.

Navigation in Components

Using useNavigate Hook

Programmatic navigation using the useNavigate hook:

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

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

  const handleAdd = () => {
    navigate("/products/add");
  };

  const handleEdit = (id: string) => {
    navigate(`/products/edit/${id}`);
  };

  const handleBack = () => {
    navigate("/products");
  };

  // Navigate with state
  const handleView = (product: Product) => {
    navigate("/products/view", { state: { product } });
  };

  // Navigate with replace (no history entry)
  const handleLogin = () => {
    navigate("/dashboard", { replace: true });
  };

  return (
    <div>
      <button onClick={handleAdd}>Add Product</button>
      <button onClick={() => handleEdit("123")}>Edit Product</button>
      <button onClick={handleBack}>Back</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Using Route Parameters

Access route parameters using useParams:

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

function EditProduct() {
  const { id } = useParams<{ id: string }>();

  // Fetch product data using id
  const { data: product } = useGetProductQuery(id!);

  return (
    <div>
      <h1>Editing product: {id}</h1>
      {/* Edit form */}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Using Location State

Access state passed via navigation:

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

function ViewProduct() {
  const location = useLocation();
  const product = location.state?.product;

  return (
    <div>
      <h1>{product?.name}</h1>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Using Link Component

Creating navigation links with Link and NavLink:

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

function SideBar() {
  return (
    <nav>
      {/* Basic Link */}
      <Link to="/">Home</Link>
      <Link to="/products">Products</Link>
      <Link to="/categories">Categories</Link>

      {/* NavLink with active styling */}
      <NavLink
        to="/products"
        className={({ isActive }) =>
          isActive ? "active-link" : "inactive-link"
        }
      >
        Products
      </NavLink>

      {/* NavLink with custom active class */}
      <NavLink
        to="/categories"
        className={({ isActive }) =>
          `nav-link ${isActive ? "active" : ""}`
        }
      >
        Categories
      </NavLink>
    </nav>
  );
}
Enter fullscreen mode Exit fullscreen mode

Link vs NavLink

  • Link - Basic navigation link
  • NavLink - Link with active state styling (useful for navigation menus)

Protected Routes

Implementing route protection for authenticated users:

import { Navigate, Outlet } from "react-router-dom";
import { useSelector } from "react-redux";

function ProtectedRoute() {
  const isAuthenticated = useSelector((state: RootState) => state.auth.isAuthenticated);

  if (!isAuthenticated) {
    return <Navigate to="/login" replace />;
  }

  return <Outlet />;
}

// Usage in App.tsx
<Route element={<ProtectedRoute />}>
  <Route path="/products" element={<Products />} />
  <Route path="/categories" element={<Categories />} />
  <Route path="/dashboard" element={<Dashboard />} />
</Route>
Enter fullscreen mode Exit fullscreen mode

Advanced Protected Route

With role-based access control:

import { Navigate, Outlet, useLocation } from "react-router-dom";
import { useSelector } from "react-redux";

interface ProtectedRouteProps {
  allowedRoles?: string[];
}

function ProtectedRoute({ allowedRoles }: ProtectedRouteProps) {
  const isAuthenticated = useSelector((state: RootState) => state.auth.isAuthenticated);
  const userRole = useSelector((state: RootState) => state.auth.user?.role);
  const location = useLocation();

  if (!isAuthenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  if (allowedRoles && !allowedRoles.includes(userRole)) {
    return <Navigate to="/unauthorized" replace />;
  }

  return <Outlet />;
}

// Usage
<Route element={<ProtectedRoute allowedRoles={["admin", "manager"]} />}>
  <Route path="/admin" element={<AdminPanel />} />
</Route>
Enter fullscreen mode Exit fullscreen mode

Route Parameters and Query Strings

Route Parameters

Define dynamic routes with parameters:

// Route definition
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/products/:category/:id" element={<ProductDetail />} />

// Access parameters
function ProductDetail() {
  const { id, category } = useParams<{ id: string; category?: string }>();

  return <div>Product ID: {id}, Category: {category}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Query Parameters

Handle URL search parameters:

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

function Products() {
  const [searchParams, setSearchParams] = useSearchParams();
  const page = searchParams.get("page") || "1";
  const category = searchParams.get("category") || "";

  const handleFilter = (category: string) => {
    setSearchParams({ category, page: "1" });
  };

  return (
    <div>
      <button onClick={() => handleFilter("electronics")}>
        Filter Electronics
      </button>
      <p>Current page: {page}</p>
      <p>Current category: {category}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Handling 404 Pages

Create a custom 404 page:

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

function NotFound() {
  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      <h1 className="text-4xl font-bold mb-4">404 - Page Not Found</h1>
      <p className="text-gray-600 mb-4">The page you're looking for doesn't exist.</p>
      <Link to="/" className="px-4 py-2 bg-blue-500 text-white rounded">
        Go Home
      </Link>
    </div>
  );
}

// Usage - must be last route
<Route path="*" element={<NotFound />} />
Enter fullscreen mode Exit fullscreen mode

Nested Routes with Multiple Levels

Create deeply nested routes:

<Route path="/dashboard" element={<DashboardLayout />}>
  <Route index element={<DashboardHome />} />
  <Route path="products" element={<ProductsLayout />}>
    <Route index element={<ProductsList />} />
    <Route path="add" element={<AddProduct />} />
    <Route path="edit/:id" element={<EditProduct />} />
  </Route>
  <Route path="settings" element={<Settings />} />
</Route>
Enter fullscreen mode Exit fullscreen mode

Programmatic Navigation Patterns

Navigate After Action

function AddProduct() {
  const navigate = useNavigate();
  const [addProduct] = useAddProductMutation();

  const handleSubmit = async (data: ProductFormData) => {
    try {
      await addProduct(data).unwrap();
      navigate("/products", { replace: true });
    } catch (error) {
      console.error("Failed to add product:", error);
    }
  };

  return <ProductForm onSubmit={handleSubmit} />;
}
Enter fullscreen mode Exit fullscreen mode

Navigate with State

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

  const handleView = (product: Product) => {
    navigate("/products/view", {
      state: { product, returnPath: "/products" }
    });
  };

  return (
    <div>
      {products.map(product => (
        <button key={product.id} onClick={() => handleView(product)}>
          View {product.name}
        </button>
      ))}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Use BrowserRouter - For client-side routing in production
  2. Organize routes by feature - Group related routes together
  3. Use Outlet for layouts - Create reusable layout components
  4. Implement protected routes - Secure authenticated pages
  5. Use NavLink for navigation - Active state styling
  6. Handle 404 routes - Provide helpful error pages
  7. Use route parameters - For dynamic routes
  8. Type your routes - Use TypeScript for type safety
  9. Lazy load routes - Use React.lazy for code splitting
  10. Use Navigate for redirects - Declarative redirects

Code Splitting with Lazy Loading

import { lazy, Suspense } from "react";

const Products = lazy(() => import("./pages/Products/Products"));
const Categories = lazy(() => import("./pages/Categories/Categories"));

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route
          path="/products"
          element={
            <Suspense fallback={<div>Loading...</div>}>
              <Products />
            </Suspense>
          }
        />
      </Routes>
    </BrowserRouter>
  );
}
Enter fullscreen mode Exit fullscreen mode

Common Patterns

Redirect After Login

function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  const from = location.state?.from?.pathname || "/dashboard";

  const handleLogin = async (credentials: LoginData) => {
    await login(credentials);
    navigate(from, { replace: true });
  };

  return <LoginForm onSubmit={handleLogin} />;
}
Enter fullscreen mode Exit fullscreen mode

Conditional Navigation

function ProductCard({ product }: { product: Product }) {
  const navigate = useNavigate();
  const isAuthenticated = useSelector((state: RootState) => state.auth.isAuthenticated);

  const handleClick = () => {
    if (isAuthenticated) {
      navigate(`/products/${product.id}`);
    } else {
      navigate("/login", { state: { from: `/products/${product.id}` } });
    }
  };

  return <div onClick={handleClick}>{product.name}</div>;
}
Enter fullscreen mode Exit fullscreen mode

Resources and Further Reading

Conclusion

React Router DOM provides powerful routing capabilities for React applications. With nested routes, protected routes, and programmatic navigation, you can create complex navigation structures for inventory management systems and other React applications.

Key Takeaways:

  • React Router DOM enables client-side routing without page refreshes
  • Nested routes with Outlet for shared layouts
  • Protected routes for authentication-based access control
  • Route parameters for dynamic routes like /products/:id
  • Programmatic navigation with useNavigate hook
  • Link and NavLink for navigation menus
  • Query parameters for filtering and search
  • 404 handling for better user experience

Whether you're building a simple single-page app or a complex multi-page application, React Router provides the foundation you need. It handles all the routing logic while giving you complete control over navigation and route protection.


What's your experience with React Router? Share your tips and tricks in the comments below! πŸš€


πŸ’‘ Looking for more details? This is a condensed version of my comprehensive guide. Read the full article on my blog for additional examples, advanced patterns, troubleshooting tips, and more in-depth explanations.

If you found this guide helpful, consider checking out my other articles on React development and frontend development best practices.

Top comments (0)