Welcome to React Router!
Imagine walking through a museum where instead of moving between rooms, the room transforms around you as you choose different exhibits. That's client-side routing! The URL changes, but the page doesn't reload - content simply appears and disappears, creating a smooth, app-like experience.
Traditional vs Client-side Routing
Traditional Server-side Routing
- User clicks a link
- Browser sends request to server
- Server processes request
- Server sends new HTML page
- Browser reloads entire page
Client-side Routing
- User clicks a link
- React Router intercepts click
- URL changes without reload
- Router renders new component
- Content updates instantly
Setting Up React Router
// Installation
npm install react-router-dom
// Basic Setup
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
// Components
function Home() {
return <h1>Welcome Home!</h1>;
}
function About() {
return <h1>About Us</h1>;
}
function Contact() {
return <h1>Contact Us</h1>;
}
function NotFound() {
return <h1>404 - Page Not Found</h1>;
}
// App with routing
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
Core Router Components
1. BrowserRouter
The foundation that enables routing in your app. It uses HTML5 history API to keep UI in sync with URL.
import { BrowserRouter } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
{/* All your app components go here */}
</BrowserRouter>
);
}
2. Routes and Route
Define your application's route structure.
import { Routes, Route } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserProfile />} />
<Route path="/dashboard/*" element={<Dashboard />} />
</Routes>
);
}
3. Link Component
Navigate without page reloads - the magic of client-side routing!
import { Link } from 'react-router-dom';
function Navigation() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
);
}
// Comparison with regular anchor tags
function BadNavigation() {
return (
<nav>
{/* ❌ These cause full page reloads! */}
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
);
}
4. NavLink Component
Like Link, but with styling capabilities for active routes.
import { NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
<NavLink
to="/"
className={({ isActive }) => isActive ? 'active' : ''}
>
Home
</NavLink>
<NavLink
to="/about"
style={({ isActive }) => ({
color: isActive ? 'red' : 'black',
fontWeight: isActive ? 'bold' : 'normal'
})}
>
About
</NavLink>
</nav>
);
}
Dynamic Routes and Parameters
URL Parameters
// Route definition
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />
// Accessing parameters
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams();
return <h1>User Profile: {userId}</h1>;
}
function Comment() {
const { postId, commentId } = useParams();
return (
<div>
<h2>Post: {postId}</h2>
<h3>Comment: {commentId}</h3>
</div>
);
}
Query Parameters
// Reading query parameters
import { useSearchParams } from 'react-router-dom';
function SearchResults() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q');
const page = searchParams.get('page') || 1;
const sort = searchParams.get('sort') || 'relevance';
// Update query parameters
const updatePage = (newPage) => {
setSearchParams(prev => {
prev.set('page', newPage);
return prev;
});
};
return (
<div>
<h1>Search Results for: {query}</h1>
<p>Page: {page}, Sort: {sort}</p>
<button onClick={() => updatePage(parseInt(page) + 1)}>
Next Page
</button>
</div>
);
}
// Creating links with query parameters
function SearchForm() {
const [query, setQuery] = useState('');
return (
<form>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<Link to={`/search?q=${encodeURIComponent(query)}`}>
Search
</Link>
</form>
);
}
Protected Routes
// Protected Route Component
function ProtectedRoute({ children }) {
const { user, loading } = useAuth();
const location = useLocation();
if (loading) {
return <LoadingSpinner />;
}
if (!user) {
// Redirect to login, but save the attempted URL
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// Usage
function App() {
return (
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
</Routes>
);
}
// Role-based Route Protection
function RoleBasedRoute({ children, allowedRoles }) {
const { user } = useAuth();
if (!user || !allowedRoles.includes(user.role)) {
return <Navigate to="/unauthorized" replace />;
}
return children;
}
// Usage
<Route
path="/admin"
element={
<RoleBasedRoute allowedRoles={['admin']}>
<AdminPanel />
</RoleBasedRoute>
}
/>
Layouts and Nested Routes
// Layout Component
function MainLayout() {
return (
<div className="layout">
<Header />
<nav>
<Link to="/">Home</Link>
<Link to="/products">Products</Link>
<Link to="/about">About</Link>
</nav>
<main>
<Outlet /> {/* Child routes render here */}
</main>
<Footer />
</div>
);
}
// App with Layout
function App() {
return (
<Routes>
<Route element={<MainLayout />}>
<Route path="/" element={<Home />} />
<Route path="products" element={<Products />} />
<Route path="about" element={<About />} />
</Route>
{/* Different layout for auth pages */}
<Route element={<AuthLayout />}>
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
</Route>
{/* No layout */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}
Error Handling in Routes
// Error Boundary for Routes
import { useRouteError } from 'react-router-dom';
function ErrorBoundary() {
const error = useRouteError();
return (
<div className="error-page">
<h1>Oops! Something went wrong</h1>
<p>{error.statusText || error.message}</p>
<Link to="/">Go back home</Link>
</div>
);
}
// Using Error Boundary
function App() {
return (
<Routes>
<Route
path="/"
element={<Home />}
errorElement={<ErrorBoundary />}
/>
<Route
path="/users/:id"
element={<UserProfile />}
errorElement={<ErrorBoundary />}
/>
</Routes>
);
}
// Handling 404s
function NotFound() {
return (
<div className="not-found">
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<Link to="/">Go to Homepage</Link>
</div>
);
}
// Catch-all route for 404s
<Route path="*" element={<NotFound />} />
React Router Best Practices
1. Use Descriptive Route Paths
// ✅ Good - descriptive and hierarchical
<Route path="/users/:userId/settings" element={<UserSettings />} />
<Route path="/products/:category/:productId" element={<ProductDetail />} />
// ❌ Bad - unclear and flat
<Route path="/s/:id" element={<UserSettings />} />
<Route path="/p/:id" element={<ProductDetail />} />
2. Handle Loading States
function UserProfile() {
const { userId } = useParams();
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId)
.then(setUser)
.finally(() => setLoading(false));
}, [userId]);
if (loading) {
return <LoadingSpinner />;
}
if (!user) {
return <Navigate to="/404" replace />;
}
return <UserProfileContent user={user} />;
}
3. Use Route Groups for Organization
// Group related routes
function App() {
return (
<Routes>
{/* Public routes */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* Auth routes */}
<Route path="/auth">
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
<Route path="forgot-password" element={<ForgotPassword />} />
</Route>
{/* Protected routes */}
<Route element={<ProtectedRoute />}>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Route>
</Routes>
);
}
Practice Exercises
Exercise 1: Build a Blog Navigation
Create a blog with the following routes:
- Home page with recent posts
- Individual post pages (/posts/:postId)
- Category pages (/categories/:categoryName)
- Author pages (/authors/:authorId)
- Search results page with query parameters
Exercise 2: Create a Multi-step Form
Build a multi-step form using routes:
- /signup/personal-info
- /signup/account-details
- /signup/preferences
- /signup/confirmation
Include navigation between steps and prevent access to later steps until earlier ones are complete.
Exercise 3: Implement Route Guards
Create different types of route protection:
- Authentication guard
- Role-based access control
- Subscription-based access
Real-World Example: E-commerce Routes
// Complete routing structure for an e-commerce app
function EcommerceApp() {
return (
<BrowserRouter>
<Routes>
{/* Public routes */}
<Route element={<MainLayout />}>
<Route path="/" element={<HomePage />} />
<Route path="/products" element={<ProductList />} />
<Route path="/products/:productId" element={<ProductDetail />} />
<Route path="/categories/:categorySlug" element={<CategoryPage />} />
<Route path="/search" element={<SearchResults />} />
<Route path="/cart" element={<Cart />} />
</Route>
{/* Auth routes */}
<Route element={<AuthLayout />}>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<Route path="/forgot-password" element={<ForgotPassword />} />
</Route>
{/* Protected routes */}
<Route element={<ProtectedRoute />}>
<Route element={<MainLayout />}>
<Route path="/account" element={<Account />}>
<Route index element={<AccountOverview />} />
<Route path="orders" element={<OrderHistory />} />
<Route path="orders/:orderId" element={<OrderDetail />} />
<Route path="addresses" element={<AddressBook />} />
<Route path="payment-methods" element={<PaymentMethods />} />
</Route>
<Route path="/checkout" element={<Checkout />}>
<Route path="shipping" element={<ShippingStep />} />
<Route path="payment" element={<PaymentStep />} />
<Route path="review" element={<ReviewStep />} />
<Route path="confirmation" element={<Confirmation />} />
</Route>
</Route>
</Route>
{/* Admin routes */}
<Route element={<RoleBasedRoute allowedRoles={['admin']} />}>
<Route path="/admin" element={<AdminLayout />}>
<Route index element={<AdminDashboard />} />
<Route path="products" element={<ProductManagement />} />
<Route path="orders" element={<OrderManagement />} />
<Route path="users" element={<UserManagement />} />
</Route>
</Route>
{/* Error routes */}
<Route path="/404" element={<NotFound />} />
<Route path="/500" element={<ServerError />} />
<Route path="*" element={<Navigate to="/404" replace />} />
</Routes>
</BrowserRouter>
);
}
Key Takeaways
- Client-side routing provides seamless navigation without page reloads
- React Router uses components to define route structure
- Use Link/NavLink for navigation, not anchor tags
- Hooks like useNavigate, useParams, and useLocation provide routing functionality
- Protect routes with authentication and authorization
- Handle errors and 404s gracefully