React is a single-page application (SPA) library. Without routing, you can only render one view at a time. Client-side routing lets you map URL paths to components without a full page reload. The dominant library is react-router-dom (v6+).
Install with npm install react-router-dom. The core components are:
BrowserRouter — wraps the app and listens to URL changesRoutes / Route — declarative route definitionsLink / NavLink — navigation elementsimport { BrowserRouter, Routes, Route, Link } from "react-router-dom";
import Home from "./pages/Home";
import About from "./pages/About";
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>
);
}
NavLink adds a className or style callback when the link matches the current URL, making it easy to highlight the active page.
<NavLink
to="/dashboard"
className={({ isActive }) => (isActive ? "active" : "")}
>
Dashboard
</NavLink>
Routes can be nested to create layouts that share a common UI. Use <Outlet /> to render the child route inside a parent layout.
import { Outlet, Link } from "react-router-dom";
function DashboardLayout() {
return (
<div>
<h2>Dashboard</h2>
<nav>
<Link to="profile">Profile</Link>
<Link to="settings">Settings</Link>
</nav>
<Outlet /> {/* child routes render here */}
</div>
);
}
// Route definition
<Route path="dashboard" element={<DashboardLayout />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
Outlet injects the child content.Use a colon (:) in the path to define a parameter segment. The useParams hook returns an object of matched parameters.
import { useParams } from "react-router-dom";
function UserProfile() {
const { username } = useParams();
return <h1>Profile of {username}</h1>;
}
// Route: <Route path="/user/:username" element={<UserProfile />} />
// URL: /user/snehal → username = "snehal"
Sometimes you need to navigate after an event (e.g. form submission). The useNavigate hook returns a navigation function.
import { useNavigate } from "react-router-dom";
function LoginPage() {
const navigate = useNavigate();
const handleLogin = async () => {
await login();
navigate("/dashboard", { replace: true });
};
return <button onClick={handleLogin}>Log In</button>;
}
A route with path="*" matches any undefined path. Place it last inside Routes.
<Route path="*" element={<NotFound />} />
Create a wrapper component that checks authentication and either renders the child route or redirects to login.
import { Navigate } from "react-router-dom";
function ProtectedRoute({ children }) {
const isAuth = useAuth(); // your auth hook
return isAuth ? children : <Navigate to="/login" replace />;
}
// Usage
<Route path="/admin" element={
<ProtectedRoute><Admin /></ProtectedRoute>
} />
Use the useSearchParams hook to read and update query string parameters (e.g. ?page=2&sort=name).
import { useSearchParams } from "react-router-dom";
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const page = searchParams.get("page") || "1";
const nextPage = () => {
setSearchParams({ page: String(Number(page) + 1) });
};
return (
<div>
<h2>Products — Page {page}</h2>
<button onClick={nextPage}>Next</button>
</div>
);
}
Combine React.lazy and Suspense with React Router to code-split at the route level.
const Dashboard = React.lazy(() => import("./pages/Dashboard"));
<Routes>
<Route path="/dashboard" element={
<React.Suspense fallback={<div>Loading...</div>}>
<Dashboard />
</React.Suspense>
} />
</Routes>
react-router-dom./article/:slug page.NavLink-based navigation bar with active styling.user state is null.Challenge: Use useSearchParams to implement a search box that filters articles by title.