Routing
React Router for navigation, route protection, URL management, and building single-page applications with multiple views.
React Router Setup
Installation and Basic Setup
npm install react-router-dom
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>
);
}
Router Types
import {
BrowserRouter, // Uses HTML5 history API
HashRouter, // Uses hash portion of URL
MemoryRouter, // Keeps history in memory
} from 'react-router-dom';
// BrowserRouter (most common)
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</BrowserRouter>
);
}
// HashRouter (for static hosting)
function App() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</HashRouter>
);
}
Basic Routing
Route Components
import { Routes, Route } from 'react-router-dom';
function AppRoutes() {
return (
<Routes>
{/* Basic routes */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
{/* Catch-all route (404) */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}
Navigation Components
import { Link, NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
{/* Basic link */}
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
{/* NavLink with active styling */}
<NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>
Home
</NavLink>
{/* NavLink with custom active check */}
<NavLink
to="/products"
className={({ isActive, isPending }) =>
isPending ? 'pending' : isActive ? 'active' : ''
}
>
Products
</NavLink>
</nav>
);
}
Dynamic Routing
URL Parameters
import { useParams } from 'react-router-dom';
// Route definition
<Route path="/user/:id" element={<UserProfile />} />
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />
// Component using parameters
function UserProfile() {
const { id } = useParams();
return <div>User ID: {id}</div>;
}
function Comment() {
const { postId, commentId } = useParams();
return (
<div>
Post: {postId}, Comment: {commentId}
</div>
);
}
Query Parameters
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
const sort = searchParams.get('sort') || 'name';
const page = parseInt(searchParams.get('page') || '1');
const updateFilter = (key, value) => {
setSearchParams(prev => {
const params = new URLSearchParams(prev);
if (value) {
params.set(key, value);
} else {
params.delete(key);
}
return params;
});
};
return (
<div>
<select
value={category || ''}
onChange={e => updateFilter('category', e.target.value)}
>
<option value="">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
<select value={sort} onChange={e => updateFilter('sort', e.target.value)}>
<option value="name">Sort by Name</option>
<option value="price">Sort by Price</option>
</select>
{/* Products filtered by category and sorted */}
{/* Pagination controls */}
</div>
);
}
Optional Parameters
// Route with optional parameter
<Route path="/products/:category?" element={<ProductList />} />;
function ProductList() {
const { category } = useParams();
return (
<div>
{category ? <h1>Products in {category}</h1> : <h1>All Products</h1>}
</div>
);
}
Nested Routing
Nested Routes Structure
import { Outlet } from 'react-router-dom';
// Layout component
function Layout() {
return (
<div>
<header>
<Navigation />
</header>
<main>
<Outlet /> {/* Child routes render here */}
</main>
<footer>Footer content</footer>
</div>
);
}
// App routing
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="products" element={<ProductsLayout />}>
<Route index element={<ProductList />} />
<Route path=":id" element={<ProductDetail />} />
<Route path="new" element={<CreateProduct />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
);
}
Index Routes
// Index route renders at parent path
<Route path="/products" element={<ProductsLayout />}>
<Route index element={<ProductList />} /> {/* Renders at /products */}
<Route path="new" element={<CreateProduct />} />{' '}
{/* Renders at /products/new */}
<Route path=":id" element={<ProductDetail />} />{' '}
{/* Renders at /products/:id */}
</Route>
Programmatic Navigation
useNavigate Hook
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleSubmit = async formData => {
try {
await login(formData);
// Navigate to dashboard
navigate('/dashboard');
// Navigate with replace (doesn't add to history)
navigate('/dashboard', { replace: true });
// Navigate with state
navigate('/dashboard', {
state: { message: 'Login successful' },
});
// Go back
navigate(-1);
// Go forward
navigate(1);
} catch (error) {
console.error('Login failed');
}
};
return <form onSubmit={handleSubmit}>...</form>;
}
Navigate Component
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children }) {
const isAuthenticated = useAuth();
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
}
return children;
}
// Conditional redirect
function UserProfile() {
const user = useUser();
if (!user) {
return <Navigate to="/login" />;
}
if (!user.profile.completed) {
return <Navigate to="/complete-profile" />;
}
return <div>User Profile</div>;
}
Route Protection
Authentication Guards
import { useAuth } from '../contexts/AuthContext';
function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
if (loading) {
return <div>Loading...</div>;
}
if (!user) {
return <Navigate to="/login" replace />;
}
return children;
}
// Usage
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>;
Role-Based Access
function RequireRole({ children, requiredRole }) {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" replace />;
}
if (user.role !== requiredRole) {
return <Navigate to="/unauthorized" replace />;
}
return children;
}
// Usage
<Route
path="/admin"
element={
<RequireRole requiredRole="admin">
<AdminPanel />
</RequireRole>
}
/>;
Route Guards with Hooks
function useRequireAuth() {
const { user } = useAuth();
const navigate = useNavigate();
useEffect(() => {
if (!user) {
navigate('/login');
}
}, [user, navigate]);
return user;
}
// Usage in component
function Dashboard() {
const user = useRequireAuth();
if (!user) return null;
return <div>Dashboard for {user.name}</div>;
}
Advanced Routing
Lazy Loading
import { lazy, Suspense } from 'react';
// Lazy load components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</Suspense>
</BrowserRouter>
);
}
Route Data Loading
import { useLoaderData } from 'react-router-dom';
// Data loader function
async function productLoader({ params }) {
const response = await fetch(`/api/products/${params.id}`);
if (!response.ok) {
throw new Error('Product not found');
}
return response.json();
}
// Route with loader
<Route
path="/products/:id"
element={<ProductDetail />}
loader={productLoader}
/>;
// Component using loaded data
function ProductDetail() {
const product = useLoaderData();
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</div>
);
}
Error Boundaries for Routes
import { useRouteError } from 'react-router-dom';
function ErrorBoundary() {
const error = useRouteError();
return (
<div className="error-page">
<h1>Oops!</h1>
<p>Sorry, an unexpected error has occurred.</p>
<p>
<i>{error.statusText || error.message}</i>
</p>
</div>
);
}
// Route with error boundary
<Route
path="/products/:id"
element={<ProductDetail />}
loader={productLoader}
errorElement={<ErrorBoundary />}
/>;
Navigation Hooks
useLocation
import { useLocation } from 'react-router-dom';
function Analytics() {
const location = useLocation();
useEffect(() => {
// Track page view
analytics.track('page_view', {
path: location.pathname,
search: location.search,
hash: location.hash,
});
}, [location]);
return null;
}
// Access state passed through navigation
function Dashboard() {
const location = useLocation();
const message = location.state?.message;
return (
<div>
{message && <div className="alert">{message}</div>}
<h1>Dashboard</h1>
</div>
);
}
useMatch and useResolvedPath
import { useMatch, useResolvedPath } from 'react-router-dom';
function CustomNavLink({ to, children }) {
const resolved = useResolvedPath(to);
const match = useMatch({ path: resolved.pathname, end: true });
return (
<Link to={to} className={match ? 'active' : ''}>
{children}
</Link>
);
}
Navigation Patterns
Breadcrumbs
import { useLocation } from 'react-router-dom';
function Breadcrumbs() {
const location = useLocation();
const pathnames = location.pathname.split('/').filter(x => x);
return (
<nav className="breadcrumbs">
<Link to="/">Home</Link>
{pathnames.map((pathname, index) => {
const routeTo = `/${pathnames.slice(0, index + 1).join('/')}`;
const isLast = index === pathnames.length - 1;
return (
<span key={routeTo}>
{' > '}
{isLast ? (
<span>{pathname}</span>
) : (
<Link to={routeTo}>{pathname}</Link>
)}
</span>
);
})}
</nav>
);
}
Tab Navigation
import { NavLink, Outlet } from 'react-router-dom';
function ProfileTabs() {
return (
<div>
<nav className="tabs">
<NavLink
to="info"
className={({ isActive }) => (isActive ? 'active' : '')}
>
Personal Info
</NavLink>
<NavLink
to="settings"
className={({ isActive }) => (isActive ? 'active' : '')}
>
Settings
</NavLink>
<NavLink
to="security"
className={({ isActive }) => (isActive ? 'active' : '')}
>
Security
</NavLink>
</nav>
<div className="tab-content">
<Outlet />
</div>
</div>
);
}
// Route structure
<Route path="/profile" element={<ProfileTabs />}>
<Route index element={<Navigate to="info" replace />} />
<Route path="info" element={<PersonalInfo />} />
<Route path="settings" element={<Settings />} />
<Route path="security" element={<Security />} />
</Route>;
Modal Routes
import { useNavigate, useLocation } from 'react-router-dom';
function Modal({ children }) {
const navigate = useNavigate();
const location = useLocation();
const closeModal = () => {
// Go back to previous location
navigate(location.state?.from || '/');
};
return (
<div className="modal-overlay" onClick={closeModal}>
<div className="modal-content" onClick={e => e.stopPropagation()}>
<button onClick={closeModal}>×</button>
{children}
</div>
</div>
);
}
// Modal route
<Route
path="/products/:id/reviews"
element={
<Modal>
<ProductReviews />
</Modal>
}
/>;
// Open modal with state
function ProductCard({ product }) {
const navigate = useNavigate();
const location = useLocation();
const openReviews = () => {
navigate(`/products/${product.id}/reviews`, {
state: { from: location.pathname },
});
};
return (
<div>
<h3>{product.name}</h3>
<button onClick={openReviews}>View Reviews</button>
</div>
);
}
Performance Optimization
Route-based Code Splitting
import { lazy, Suspense } from 'react';
// Lazy load route components
const routes = [
{
path: '/',
component: lazy(() => import('./pages/Home')),
},
{
path: '/about',
component: lazy(() => import('./pages/About')),
},
{
path: '/products',
component: lazy(() => import('./pages/Products')),
},
];
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
{routes.map(({ path, component: Component }) => (
<Route key={path} path={path} element={<Component />} />
))}
</Routes>
</Suspense>
</BrowserRouter>
);
}
Preloading Routes
// Preload route component on hover
function NavigationLink({ to, children }) {
const preloadRoute = () => {
// Preload the route component
import(`./pages/${to.replace('/', '')}`);
};
return (
<Link to={to} onMouseEnter={preloadRoute} onFocus={preloadRoute}>
{children}
</Link>
);
}
Best Practices
Route Organization
// routes/index.js
export const routes = [
{
path: '/',
element: <Layout />,
children: [
{ index: true, element: <Home /> },
{ path: 'about', element: <About /> },
{
path: 'products',
element: <ProductsLayout />,
children: [
{ index: true, element: <ProductList /> },
{ path: ':id', element: <ProductDetail /> },
{ path: 'new', element: <CreateProduct /> },
],
},
],
},
];
// App.js
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import { routes } from './routes';
const router = createBrowserRouter(routes);
function App() {
return <RouterProvider router={router} />;
}
Error Handling
// Generic error boundary
function RouteErrorBoundary() {
const error = useRouteError();
if (error.status === 404) {
return <NotFoundPage />;
}
if (error.status === 401) {
return <Navigate to="/login" />;
}
return <ErrorPage error={error} />;
}
Quick Reference
Basic Setup
import { BrowserRouter, Routes, Route } from 'react-router-dom';
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>;
Navigation
// Links
<Link to="/about">About</Link>
<NavLink to="/about">About</NavLink>
// Programmatic navigation
const navigate = useNavigate();
navigate('/dashboard');
navigate(-1); // Go back
Route Parameters
// Route: /user/:id
const { id } = useParams();
// Query params: /search?q=react
const [searchParams] = useSearchParams();
const query = searchParams.get('q');
Protection
// Protected route
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>