React Router
Checking access...
React Router is the standard library for adding navigation to React applications. It keeps your UI in sync with the URL, enabling multi-page experiences in single-page applications.
Installation
npm install react-router-domBasic Setup
Wrap your app with BrowserRouter and define routes:
import { BrowserRouter } from "react-router-dom";import App from "./App";
const root = ReactDOM.createRoot(document.getElementById("root"));root.render( <BrowserRouter> <App /> </BrowserRouter>);import { Routes, Route } from "react-router-dom";import Home from "./pages/Home";import About from "./pages/About";import Contact from "./pages/Contact";import NotFound from "./pages/NotFound";
function App() { return ( <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> <Route path="/contact" element={<Contact />} /> <Route path="*" element={<NotFound />} /> </Routes> );}Navigation
Use Link and NavLink instead of <a> tags — they prevent full page reloads:
import { Link, NavLink } from "react-router-dom";
function Navbar() { return ( <nav> {/* Basic link */} <Link to="/">Home</Link>
{/* NavLink adds "active" class when the route matches */} <NavLink to="/about" className={({ isActive }) => (isActive ? "active-link" : "")} > About </NavLink>
<NavLink to="/contact" style={({ isActive }) => ({ fontWeight: isActive ? "bold" : "normal", })} > Contact </NavLink> </nav> );}NavLink Active Styling
// The default class is "active"<NavLink to="/about" className="nav-link" />
/* CSS */.nav-link.active { color: blue; font-weight: bold; border-bottom: 2px solid blue;}URL Parameters
// Define the route with :param<Route path="/users/:userId" element={<UserProfile />} />
// Use useParams to read the parameterimport { useParams } from "react-router-dom";
function UserProfile() { const { userId } = useParams();
// Fetch user data based on userId const [user, setUser] = useState(null);
useEffect(() => { fetch(`/api/users/${userId}`) .then((r) => r.json()) .then(setUser); }, [userId]);
if (!user) return <div>Loading...</div>;
return ( <div> <h1>{user.name}</h1> <p>Email: {user.email}</p> </div> );}Multiple Parameters
<Route path="/products/:category/:productId" element={<ProductDetail />} />
// URL: /products/electronics/42function ProductDetail() { const { category, productId } = useParams(); return <div>Product {productId} in {category}</div>;}Query Parameters (Search Params)
import { useSearchParams } from "react-router-dom";
function SearchPage() { const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get("q") || ""; const page = parseInt(searchParams.get("page") || "1"); const sort = searchParams.get("sort") || "relevance";
const updateQuery = (newQuery) => { setSearchParams({ q: newQuery, page: "1", sort }); };
const nextPage = () => { setSearchParams({ q: query, page: String(page + 1), sort }); };
return ( <div> <input value={query} onChange={(e) => updateQuery(e.target.value)} placeholder="Search..." /> <p>Searching for "{query}" — Page {page}</p> <button onClick={nextPage}>Next Page</button> </div> );}Nested Routes
Create layout routes that wrap child routes:
function App() { return ( <Routes> <Route path="/" element={<Layout />}> <Route index element={<Home />} /> <Route path="about" element={<About />} /> <Route path="dashboard" element={<DashboardLayout />}> <Route index element={<DashboardHome />} /> <Route path="settings" element={<Settings />} /> <Route path="analytics" element={<Analytics />} /> </Route> <Route path="*" element={<NotFound />} /> </Route> </Routes> );}function Layout() { return ( <div> <Navbar /> <main> <Outlet /> {/* Child routes render here */} </main> <Footer /> </div> );}
function DashboardLayout() { return ( <div className="dashboard"> <DashboardSidebar /> <div className="content"> <Outlet /> </div> </div> );}Index Routes
The index route is the default child rendered when the parent path is matched exactly:
<Route path="/dashboard" element={<DashboardLayout />}> <Route index element={<DashboardHome />} /> {/* /dashboard */} <Route path="settings" element={<Settings />} /> {/* /dashboard/settings */} <Route path="analytics" element={<Analytics />} /> {/* /dashboard/analytics */}</Route>Programmatic Navigation
Navigate in response to events (not clicks):
import { useNavigate } from "react-router-dom";
function LoginForm() { const navigate = useNavigate();
const handleSubmit = async (e) => { e.preventDefault();
try { await login(formData); navigate("/dashboard", { replace: true }); // Navigate and replace history } catch (err) { setError(err.message); } };
return <form onSubmit={handleSubmit}>{/* ... */}</form>;}Navigate Component
For declarative redirects:
import { Navigate } from "react-router-dom";
function ProtectedRoute({ user, children }) { if (!user) { return <Navigate to="/login" replace />; } return children;}
// Usage:<Route path="/dashboard" element={ <ProtectedRoute user={user}> <Dashboard /> </ProtectedRoute> }/>Protected Routes
function RequireAuth({ children }) { const { user, loading } = useAuth(); const location = useLocation();
if (loading) { return <div>Checking authentication...</div>; }
if (!user) { // Redirect to login, but remember where they were going return <Navigate to="/login" state={{ from: location }} replace />; }
return children;}
// Usage:<Route path="/settings" element={ <RequireAuth> <Settings /> </RequireAuth> }/>
// In login form, redirect back:function LoginPage() { const navigate = useNavigate(); const location = useLocation(); const from = location.state?.from?.pathname || "/";
const handleLogin = async () => { await login(); navigate(from, { replace: true }); };
return <form onSubmit={handleLogin}>{/* ... */}</form>;}Lazy Loading Routes
import { lazy, Suspense } from "react";
const Dashboard = lazy(() => import("./pages/Dashboard"));const Settings = lazy(() => import("./pages/Settings"));const Analytics = lazy(() => import("./pages/Analytics"));
function App() { return ( <Suspense fallback={<div className="page-loader">Loading...</div>}> <Routes> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> <Route path="/analytics" element={<Analytics />} /> </Routes> </Suspense> );}useLocation
Access the current URL details:
import { useLocation } from "react-router-dom";
function CurrentRoute() { const location = useLocation();
return ( <div> <p>Pathname: {location.pathname}</p> <p>Search: {location.search}</p> <p>Hash: {location.hash}</p> <p>State: {JSON.stringify(location.state)}</p> </div> );}Quick Reference
<BrowserRouter> // Router provider (wrap app)<Routes> // Route switch (renders first match)<Route path="/" element={<Page />} /> // Route definition<Route index element={<Page />} /> // Default child route<Route path="*" element={<NotFound />} /> // 404 catch-all<Link to="/page"> // Navigation link<NavLink to="/page"> // Navigation link with active state<Outlet /> // Child route render outletuseParams() // Get URL parametersuseSearchParams() // Get/set query parametersuseNavigate() // Programmatic navigationuseLocation() // Current URL infoPractice Exercises
Multi-page blog: Create routes for Home, Blog (list), BlogPost (with :slug param), and About. Use NavLink for navigation. Show a 404 page for unknown routes.
E-commerce store: Build routes:
/products(list),/products/:id(detail),/cart,/checkout. Use nested routes with a Layout component. Add a “back to products” link on the product detail page.Auth flow: Create protected routes for
/dashboardand/settings. Redirect unauthenticated users to/login. After login, redirect them back to the page they were trying to access.