Routing & Navigation
Implement client-side routing for single-page applications
Routing & Navigation in React
Routing and navigation transform React apps from single-page islands into full-featured multi-page experiences, and mastering these concepts with free flashcards and spaced repetition will accelerate your learning journey. This lesson covers React Router fundamentals, dynamic routing patterns, navigation methods, and advanced techniques like nested routes and protected routesβessential skills for building professional React applications.
Welcome to React Routing πΊοΈ
Welcome to one of the most practical aspects of React development! If you've ever wondered how applications like Netflix or Airbnb handle different pages while staying lightning-fast, the answer is client-side routing. Unlike traditional multi-page websites that reload entirely when you click a link, React apps use routing libraries to swap components dynamically, creating seamless user experiences.
π‘ Think of routing like a GPS for your app: just as a GPS updates your view without restarting your car's computer, React Router updates your UI without refreshing the browser.
Core Concepts π»
What is Client-Side Routing?
Client-side routing means managing navigation entirely in the browser using JavaScript, without requesting new HTML documents from the server. When a user clicks a link:
- JavaScript intercepts the click
- Updates the browser's URL (using the History API)
- React renders the appropriate component
- The page appears to change instantlyβno white flash, no loading spinner
Traditional Multi-Page App:
ββββββββββββ ββββββββββββ ββββββββββββ
β Home ββGETβββ Server βββGETββ About β
β .html β β β β .html β
ββββββββββββ ββββββββββββ ββββββββββββ
β Full reload β Full reload β
π White flash π White flash π White flash
React Single-Page App (SPA):
ββββββββββββββββββββββββββββββββββββββββββ
β React App (loaded once) β
β ββββββββββββ ββββββββββββ β
β β Home β βJSββ β About β β
β βComponent β βComponent β β
β ββββββββββββ ββββββββββββ β
β β Instant swap, no reload β
β β‘ No flash β
ββββββββββββββββββββββββββββββββββββββββββ
React Router Library π
React Router is the de facto standard routing library for React. It provides components that enable declarative routingβyou describe what should render at each URL using JSX rather than imperatively managing navigation logic.
Key React Router packages:
| Package | Purpose | Use When |
|---|---|---|
react-router-dom | Web applications | Building browser-based apps (most common) |
react-router-native | React Native apps | Building mobile applications |
react-router | Core library | Usually imported automatically by the above |
Essential Router Components π§©
BrowserRouter wraps your entire application and provides routing context. Think of it as the power source for all routing features:
import { BrowserRouter } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
{/* Your app components */}
</BrowserRouter>
);
}
Routes and Route define which components render for which URLs:
import { Routes, Route } from 'react-router-dom';
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
β οΈ Important: In React Router v6+, you use <Routes> (plural) wrapping multiple <Route> components. Older versions (v5) used <Switch> instead.
Link creates navigation links that work with React Router (instead of traditional <a> tags):
import { Link } from 'react-router-dom';
<Link to="/about">About Us</Link>
π€ Did you know? Using regular <a> tags would cause full page reloads, destroying all your React state. <Link> prevents the default behavior and updates the URL without refreshing.
Dynamic Routes with URL Parameters π
Dynamic routes let you create flexible URL patterns that capture values. Use :paramName syntax:
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/products/:category/:productId" element={<ProductDetail />} />
</Routes>
Inside the component, access parameters using the useParams hook:
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams();
return <h1>Viewing profile for user {userId}</h1>;
}
URL example: /users/42 would render UserProfile with userId = "42".
π‘ Memory trick - PURE: Params Use Router's Extraction (useParams extracts route parameters)
Navigating Programmatically π
Sometimes you need to navigate after an action (like form submission). Use the useNavigate hook:
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
// ... authentication logic ...
navigate('/dashboard'); // Redirect after login
};
return <form onSubmit={handleSubmit}>...</form>;
}
Navigate options:
navigate('/path'); // Go to path
navigate(-1); // Go back one page (like browser back)
navigate(1); // Go forward one page
navigate('/path', { replace: true }); // Replace current history entry
Nested Routes πͺ
Nested routes create layouts where certain components stay visible while inner content changes:
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route path="overview" element={<Overview />} />
<Route path="settings" element={<Settings />} />
<Route path="profile" element={<Profile />} />
</Route>
</Routes>
In the parent component, use Outlet to render nested routes:
import { Outlet } from 'react-router-dom';
function DashboardLayout() {
return (
<div>
<nav>{/* Dashboard navigation */}</nav>
<main>
<Outlet /> {/* Nested routes render here */}
</main>
</div>
);
}
Nested Route Structure: βββββββββββββββββββββββββββββββββββββββ β DashboardLayout β β βββββββββββββββββββββββββββββββββ β β β Navigation (always visible) β β β βββββββββββββββββββββββββββββββββ β β βββββββββββββββββββββββββββββββββ β β β β β β ββ β β β (nested route renders β β β β Overview/Settings/Profile)β β β β β β β βββββββββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββββββββββ
Protected Routes & Conditional Rendering π
Protected routes prevent unauthorized access. Create a wrapper component:
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children, isAuthenticated }) {
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
}
// Usage:
<Route
path="/admin"
element={
<ProtectedRoute isAuthenticated={user.isLoggedIn}>
<AdminPanel />
</ProtectedRoute>
}
/>
Query Parameters & Search π
Query parameters (?key=value) don't need route definitions. Access them with useSearchParams:
import { useSearchParams } from 'react-router-dom';
function SearchResults() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q');
const filter = searchParams.get('filter');
return (
<div>
<p>Searching for: {query}</p>
<button onClick={() => setSearchParams({ q: query, filter: 'new' })}>
Apply Filter
</button>
</div>
);
}
URL example: /search?q=react&filter=popular
Navigation State & Location π
The useLocation hook provides information about the current URL:
import { useLocation } from 'react-router-dom';
function Breadcrumbs() {
const location = useLocation();
return (
<div>
<p>Current path: {location.pathname}</p>
<p>Search: {location.search}</p>
<p>Hash: {location.hash}</p>
</div>
);
}
You can pass state through navigation:
const navigate = useNavigate();
navigate('/profile', { state: { from: 'settings' } });
// In destination component:
const location = useLocation();
console.log(location.state.from); // 'settings'
Examples π―
Example 1: Basic Multi-Page Application
Let's build a simple three-page website with home, about, and contact pages:
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function Home() {
return (
<div>
<h1>Home Page π </h1>
<p>Welcome to our website!</p>
</div>
);
}
function About() {
return (
<div>
<h1>About Us π</h1>
<p>We're a team of passionate developers.</p>
</div>
);
}
function Contact() {
return (
<div>
<h1>Contact π§</h1>
<p>Email us at hello@example.com</p>
</div>
);
}
function Navigation() {
return (
<nav style={{ padding: '20px', borderBottom: '2px solid #ccc' }}>
<Link to="/" style={{ marginRight: '15px' }}>Home</Link>
<Link to="/about" style={{ marginRight: '15px' }}>About</Link>
<Link to="/contact">Contact</Link>
</nav>
);
}
function App() {
return (
<BrowserRouter>
<Navigation />
<div style={{ padding: '20px' }}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
What's happening here:
BrowserRouterwraps the entire app, enabling routingNavigationcomponent appears on every page (outsideRoutes)Linkcomponents create clickable navigation without page reloadsRoutesdefines which component renders for each URL path- Clicking links instantly swaps components without refreshing
Example 2: Dynamic Product Catalog with URL Parameters
Building an e-commerce site where products have unique IDs in the URL:
import React from 'react';
import { BrowserRouter, Routes, Route, Link, useParams } from 'react-router-dom';
const products = {
1: { name: 'Laptop', price: 999, description: 'Powerful computing' },
2: { name: 'Mouse', price: 29, description: 'Ergonomic design' },
3: { name: 'Keyboard', price: 79, description: 'Mechanical switches' }
};
function ProductList() {
return (
<div>
<h1>Products ποΈ</h1>
<ul>
{Object.keys(products).map(id => (
<li key={id}>
<Link to={`/products/${id}`}>{products[id].name}</Link>
</li>
))}
</ul>
</div>
);
}
function ProductDetail() {
const { productId } = useParams();
const product = products[productId];
if (!product) {
return <h2>Product not found π</h2>;
}
return (
<div>
<h1>{product.name}</h1>
<p><strong>Price:</strong> ${product.price}</p>
<p><strong>Description:</strong> {product.description}</p>
<Link to="/products">β Back to Products</Link>
</div>
);
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/products" element={<ProductList />} />
<Route path="/products/:productId" element={<ProductDetail />} />
</Routes>
</BrowserRouter>
);
}
export default App;
Key features:
- Dynamic route
/products/:productIdcaptures any ID useParams()extracts theproductIdfrom the URL- Template literal creates dynamic links:
`/products/${id}` - Product not found handling for invalid IDs
π§ Try this: Add a search feature using query parameters like /products?search=laptop
Example 3: Nested Dashboard with Outlet
Creating a dashboard with persistent sidebar navigation:
import React from 'react';
import { BrowserRouter, Routes, Route, Link, Outlet, useNavigate } from 'react-router-dom';
function DashboardLayout() {
return (
<div style={{ display: 'flex' }}>
<aside style={{ width: '200px', background: '#f0f0f0', padding: '20px' }}>
<h2>Dashboard</h2>
<nav>
<ul style={{ listStyle: 'none', padding: 0 }}>
<li><Link to="/dashboard/stats">Statistics π</Link></li>
<li><Link to="/dashboard/users">Users π₯</Link></li>
<li><Link to="/dashboard/settings">Settings βοΈ</Link></li>
</ul>
</nav>
</aside>
<main style={{ flex: 1, padding: '20px' }}>
<Outlet /> {/* Nested routes render here */}
</main>
</div>
);
}
function Stats() {
return <h1>Statistics Dashboard π</h1>;
}
function Users() {
return <h1>User Management π₯</h1>;
}
function Settings() {
const navigate = useNavigate();
const handleSave = () => {
alert('Settings saved!');
navigate('/dashboard/stats');
};
return (
<div>
<h1>Settings βοΈ</h1>
<button onClick={handleSave}>Save & Return to Stats</button>
</div>
);
}
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route path="stats" element={<Stats />} />
<Route path="users" element={<Users />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</BrowserRouter>
);
}
export default App;
Architecture breakdown:
DashboardLayoutrenders once, sidebar stays visible<Outlet />acts as placeholder for nested routes- Nested routes don't include parent path (
statsnot/dashboard/stats) useNavigateenables programmatic navigation after actions- The layout persists while inner content swaps
Example 4: Protected Routes with Authentication
Implementing authentication-protected admin routes:
import React, { useState } from 'react';
import { BrowserRouter, Routes, Route, Link, Navigate, useNavigate } from 'react-router-dom';
function ProtectedRoute({ children, isAuthenticated }) {
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
}
function Login({ onLogin }) {
const navigate = useNavigate();
const [username, setUsername] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (username) {
onLogin(username);
navigate('/admin');
}
};
return (
<form onSubmit={handleSubmit}>
<h1>Login π</h1>
<input
type="text"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
<button type="submit">Login</button>
</form>
);
}
function AdminPanel({ user, onLogout }) {
return (
<div>
<h1>Admin Panel π¨βπΌ</h1>
<p>Welcome, {user}!</p>
<button onClick={onLogout}>Logout</button>
</div>
);
}
function Home() {
return (
<div>
<h1>Home π </h1>
<Link to="/admin">Go to Admin Panel</Link>
</div>
);
}
function App() {
const [user, setUser] = useState(null);
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login onLogin={setUser} />} />
<Route
path="/admin"
element={
<ProtectedRoute isAuthenticated={!!user}>
<AdminPanel user={user} onLogout={() => setUser(null)} />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
Security flow:
- User tries accessing
/admin ProtectedRoutechecksisAuthenticated- If false,
<Navigate to="/login" replace />redirects - After login,
navigate('/admin')takes user to protected page replaceoption prevents back-button bypass
β οΈ Security note: This is client-side protection only! Always validate authentication on your backend server.
Common Mistakes β οΈ
1. Using <a> Tags Instead of <Link>
β Wrong:
<a href="/about">About</a> // Full page reload!
β Correct:
<Link to="/about">About</Link> // Client-side navigation
2. Forgetting to Wrap App with BrowserRouter
β Wrong:
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
</Routes>
);
}
Error: "useRoutes() may be used only in the context of a
β Correct:
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
);
}
3. Incorrect Nested Route Paths
β Wrong:
<Route path="/dashboard" element={<Layout />}>
<Route path="/dashboard/settings" element={<Settings />} /> // Redundant!
</Route>
β Correct:
<Route path="/dashboard" element={<Layout />}>
<Route path="settings" element={<Settings />} /> // Relative path
</Route>
4. Missing Outlet in Parent Component
β Wrong:
function Layout() {
return (
<div>
<nav>Navigation</nav>
{/* Forgot <Outlet /> - nested routes won't render! */}
</div>
);
}
β Correct:
function Layout() {
return (
<div>
<nav>Navigation</nav>
<Outlet /> {/* Nested routes render here */}
</div>
);
}
5. Using navigate() Outside Router Context
β Wrong:
import { useNavigate } from 'react-router-dom';
function externalFunction() {
const navigate = useNavigate(); // Error! Not in Router context
navigate('/home');
}
β Correct:
// Only call hooks inside components within <BrowserRouter>
function MyComponent() {
const navigate = useNavigate(); // β Works!
const handleClick = () => navigate('/home');
return <button onClick={handleClick}>Go Home</button>;
}
6. Not Handling 404 Routes
β Missing:
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* No catch-all route! */}
</Routes>
β Correct:
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} /> {/* Catches all unmatched routes */}
</Routes>
Key Takeaways π―
React Router enables client-side navigation without page reloads, creating fast, seamless user experiences
BrowserRouter wraps your app and must be at the top level to provide routing context
Use Link instead of tags to prevent full page reloads and maintain React state
Dynamic routes use colon syntax (
:paramName) anduseParams()extracts values from URLsNested routes with Outlet create persistent layouts where parent components stay visible
useNavigate enables programmatic navigation after events like form submissions or API calls
Protected routes use conditional rendering with
<Navigate />to redirect unauthorized usersQuery parameters don't need route definitionsβaccess them with
useSearchParams()Always include a catch-all route (
path="*") to handle 404 scenarios gracefullyRouter hooks only work inside components wrapped by
<BrowserRouter>, not in external functions
π Quick Reference Card
| Component | Purpose |
<BrowserRouter> | Wraps app, provides routing context |
<Routes> | Container for route definitions |
<Route> | Defines path-to-component mapping |
<Link> | Navigation link (replaces <a>) |
<Outlet> | Renders nested routes in parent |
<Navigate> | Redirects to different route |
useParams() | Extracts URL parameters |
useNavigate() | Programmatic navigation function |
useLocation() | Current URL location info |
useSearchParams() | Read/write query parameters |
Common Patterns:
path="/" | Home route |
path="/about" | Static route |
path="/users/:id" | Dynamic parameter |
path="*" | 404 catch-all |
navigate(-1) | Go back |
navigate('/path', {replace: true}) | Replace history |
π Further Study
React Router Official Documentation: https://reactrouter.com/ - Comprehensive guides and API reference with examples
MDN History API: https://developer.mozilla.org/en-US/docs/Web/API/History_API - Understanding the browser API powering React Router
React Router Tutorial by Web Dev Simplified: https://www.youtube.com/watch?v=Ul3y1LXxzdU - Video walkthrough of routing fundamentals and advanced patterns