React Router

Client-side routing and navigation

What Is React Router?

React Router is the standard library for routing in React applications. It enables client-side navigation, allowing users to move between pages without full page reloads—making your app feel fast and responsive like a native app.

Installation

npm install react-router-dom

Basic Setup

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

// Page components
function Home() {
  return <h1>Home Page</h1>;
}

function About() {
  return <h1>About Page</h1>;
}

function Contact() {
  return <h1>Contact Page</h1>;
}

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

Navigation with Link

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

function Navbar() {
  return (
    <nav>
      {/* Basic Link */}
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
      
      {/* NavLink - adds active class automatically */}
      <NavLink 
        to="/" 
        className={({ isActive }) => isActive ? 'active' : ''}
      >
        Home
      </NavLink>
      
      <NavLink 
        to="/about"
        style={({ isActive }) => ({
          fontWeight: isActive ? 'bold' : 'normal',
          color: isActive ? 'blue' : 'gray'
        })}
      >
        About
      </NavLink>
    </nav>
  );
}

⚠️ Link vs <a> Tag

Always use <Link> for internal navigation. Regular <a> tags cause full page reloads, losing React state and performance benefits.

Dynamic Routes (URL Parameters)

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

// Route definition
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />

// Component accessing params
function UserProfile() {
  const { userId } = useParams();
  
  return <h1>User ID: {userId}</h1>;
}

// Multiple params
function Comment() {
  const { postId, commentId } = useParams();
  
  return (
    <div>
      <p>Post: {postId}</p>
      <p>Comment: {commentId}</p>
    </div>
  );
}

// Linking with params
<Link to="/users/123">View User 123</Link>
<Link to={`/users/${user.id}`}>View {user.name}</Link>

Programmatic Navigation

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

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

  const handleSubmit = async (e) => {
    e.preventDefault();
    
    const success = await loginUser(credentials);
    
    if (success) {
      // Navigate to dashboard
      navigate('/dashboard');
      
      // Navigate with replace (no back button)
      navigate('/dashboard', { replace: true });
      
      // Go back
      navigate(-1);
      
      // Go forward
      navigate(1);
    }
  };

  return <form onSubmit={handleSubmit}>...</form>;
}

// Navigate with state
function ProductCard({ product }) {
  const navigate = useNavigate();

  const handleClick = () => {
    navigate(`/products/${product.id}`, { 
      state: { fromHome: true, product } 
    });
  };

  return <button onClick={handleClick}>View Details</button>;
}

Nested Routes

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

// Layout component with Outlet
function DashboardLayout() {
  return (
    <div className="dashboard">
      <Sidebar />
      <main>
        <Outlet />  {/* Child routes render here */}
      </main>
    </div>
  );
}

// Route configuration
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        
        {/* Nested routes */}
        <Route path="/dashboard" element={<DashboardLayout />}>
          <Route index element={<DashboardHome />} />
          <Route path="profile" element={<Profile />} />
          <Route path="settings" element={<Settings />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

// URLs:
// /dashboard → DashboardLayout + DashboardHome
// /dashboard/profile → DashboardLayout + Profile
// /dashboard/settings → DashboardLayout + Settings

Query Parameters

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

function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  
  // Read query params
  const query = searchParams.get('q') || '';
  const page = parseInt(searchParams.get('page')) || 1;
  const sort = searchParams.get('sort') || 'newest';

  // Update query params
  const handleSearch = (newQuery) => {
    setSearchParams({ q: newQuery, page: 1 });
  };

  const handlePageChange = (newPage) => {
    setSearchParams({ q: query, page: newPage, sort });
  };

  return (
    <div>
      <input 
        value={query}
        onChange={(e) => handleSearch(e.target.value)}
      />
      <p>Page: {page}</p>
      <button onClick={() => handlePageChange(page + 1)}>Next</button>
    </div>
  );
}

// URL: /search?q=react&page=2&sort=newest

Protected Routes

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

// Protected route wrapper
function ProtectedRoute({ children }) {
  const { user } = useAuth();  // Your auth hook
  const location = useLocation();

  if (!user) {
    // Redirect to login, save attempted URL
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

// Usage in routes
<Routes>
  <Route path="/login" element={<Login />} />
  <Route 
    path="/dashboard" 
    element={
      <ProtectedRoute>
        <Dashboard />
      </ProtectedRoute>
    } 
  />
</Routes>

// Redirect back after login
function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  const from = location.state?.from?.pathname || '/';

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

404 Not Found Page

function NotFound() {
  return (
    <div>
      <h1>404 - Page Not Found</h1>
      <Link to="/">Go Home</Link>
    </div>
  );
}

// Catch-all route (must be last)
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="*" element={<NotFound />} />
</Routes>

Location State

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

// Passing state
<Link to="/checkout" state={{ items: cartItems }}>
  Checkout
</Link>

// Receiving state
function Checkout() {
  const location = useLocation();
  const { items } = location.state || { items: [] };

  return (
    <div>
      {items.map(item => (
        <div key={item.id}>{item.name}</div>
      ))}
    </div>
  );
}

🎯 React Router Best Practices

  • ✓ Always use Link/NavLink for internal navigation
  • ✓ Use nested routes for shared layouts
  • ✓ Handle 404 with a catch-all route
  • ✓ Use useNavigate for programmatic navigation
  • ✓ Protect sensitive routes with auth checks
  • ✓ Use query params for filterable/shareable state
  • ✓ Consider React Router's data loading features (loader, action)