Skip to main content

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>
);
}
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>;
}
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 />}
/>;

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>
);
}
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>;
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>;
// 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>
}
/>