React Router
React Router enables client-side navigation in single-page applications.
Setting Up Routes
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}
Dynamic Segments
Use parameters in the URL:
<Route path="/users/:id" element={<UserProfile />} />
function UserProfile() {
const { id } = useParams();
return <h1>User {id}</h1>;
}
Navigating Programmatically
import { useNavigate } from "react-router-dom";
function LoginButton() {
const navigate = useNavigate();
const handleLogin = async () => {
await loginUser();
navigate("/dashboard");
};
return <button onClick={handleLogin}>Login</button>;
}
Nested Routes
<Route path="/dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<Outlet />
</div>
);
}
Full example
A complete multi-page app: a shared layout, an index route, a dynamic profile page, and a catch-all 404.
jsxsrc/main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.jsx";
import "./index.css";
createRoot(document.getElementById("root")).render(
<StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</StrictMode>
);
jsxsrc/App.jsx
import { Routes, Route } from "react-router-dom";
import Layout from "./components/Layout.jsx";
import Home from "./pages/Home.jsx";
import About from "./pages/About.jsx";
import UserProfile from "./pages/UserProfile.jsx";
import NotFound from "./pages/NotFound.jsx";
export default function App() {
return (
<Routes>
<Route element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="users/:id" element={<UserProfile />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
);
}
jsxsrc/components/Layout.jsx
import { NavLink, Outlet } from "react-router-dom";
export default function Layout() {
return (
<div className="app">
<header className="nav">
<NavLink to="/">Home</NavLink>
<NavLink to="/about">About</NavLink>
<NavLink to="/users/1">Profile</NavLink>
</header>
<main>
<Outlet />
</main>
</div>
);
}
jsxsrc/pages/Home.jsx
export default function Home() {
return <h1>Welcome home</h1>;
}
jsxsrc/pages/About.jsx
export default function About() {
return <h1>About this app</h1>;
}
jsxsrc/pages/UserProfile.jsx
import { useParams, useNavigate } from "react-router-dom";
export default function UserProfile() {
const { id } = useParams();
const navigate = useNavigate();
return (
<section>
<h1>User #{id}</h1>
<button onClick={() => navigate("/")}>Back home</button>
</section>
);
}
jsxsrc/pages/NotFound.jsx
import { Link } from "react-router-dom";
export default function NotFound() {
return (
<section>
<h1>404 — page not found</h1>
<Link to="/">Go home</Link>
</section>
);
}
Best Practices
- Keep route definitions in one place
- Use
Linkinstead of<a>tags for navigation - Use nested routes for related pages
- Handle 404 with a catch-all route (
path="*")
Common Mistakes
- Using
<a>tags causes full page reloads - Forgetting to wrap with
BrowserRouter - Not using
keyon dynamic routes when switching - Missing
Outletin parent routes breaks nesting