What is React Router?
React Router enables navigation between different views/pages in your React app. It keeps your UI in sync with the URL and provides a smooth single-page application (SPA) experience.
Why React Router?
- Navigate between pages without full reload
- URL parameters and query strings
- Nested routes for complex layouts
- Programmatic navigation
- Protected routes for authentication
π¦ Installation
npm install react-router-dom
# Version 6+ (recommended)
npm install react-router-dom@6
π― Basic Setup
// main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
// App.jsx
import { Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<div>
{/* Navigation */}
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
{/* Routes */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</div>
);
}
function Home() {
return <h1>Home Page</h1>;
}
function About() {
return <h1>About Page</h1>;
}
function Contact() {
return <h1>Contact Page</h1>;
}
π Link vs NavLink
import { Link, NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
{/* Regular Link */}
<Link to="/">Home</Link>
{/* NavLink - adds 'active' class to current route */}
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'active' : ''}
>
About
</NavLink>
{/* With styling */}
<NavLink
to="/products"
style={({ isActive }) => ({
color: isActive ? 'red' : 'black',
fontWeight: isActive ? 'bold' : 'normal'
})}
>
Products
</NavLink>
</nav>
);
}
π― URL Parameters
import { Routes, Route, useParams } from 'react-router-dom';
function App() {
return (
<Routes>
{/* Dynamic route with parameter */}
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId" element={<Post />} />
<Route path="/products/:category/:id" element={<Product />} />
</Routes>
);
}
// Access params with useParams
function UserProfile() {
const { userId } = useParams();
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]);
if (!user) return <div>Loading...</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
// Multiple parameters
function Product() {
const { category, id } = useParams();
return (
<div>
<p>Category: {category}</p>
<p>Product ID: {id}</p>
</div>
);
}
// Usage
<Link to="/users/123">User 123</Link>
<Link to="/products/electronics/456">Product</Link>
π Query Parameters
import { useSearchParams } from 'react-router-dom';
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
// Get query parameters
const query = searchParams.get('q') || '';
const page = searchParams.get('page') || '1';
const sort = searchParams.get('sort') || 'name';
const handleSearch = (newQuery) => {
setSearchParams({ q: newQuery, page: '1', sort });
};
const handlePageChange = (newPage) => {
setSearchParams({ q: query, page: newPage, sort });
};
return (
<div>
<input
value={query}
onChange={e => handleSearch(e.target.value)}
placeholder="Search..."
/>
<p>Query: {query}</p>
<p>Page: {page}</p>
<p>Sort: {sort}</p>
<button onClick={() => handlePageChange('2')}>Page 2</button>
</div>
);
}
// URL: /search?q=react&page=2&sort=date
π Programmatic Navigation
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
try {
await login(email, password);
// Navigate to dashboard after login
navigate('/dashboard');
// Or navigate with replace (no back button)
// navigate('/dashboard', { replace: true });
// Or go back
// navigate(-1);
} catch (error) {
alert('Login failed');
}
};
return (
<form onSubmit={handleSubmit}>
<input value={email} onChange={e => setEmail(e.target.value)} />
<input type="password" value={password} onChange={e => setPassword(e.target.value)} />
<button type="submit">Login</button>
</form>
);
}
// Navigate with state
function ProductList() {
const navigate = useNavigate();
const viewProduct = (product) => {
navigate(`/products/${product.id}`, {
state: { product } // Pass data
});
};
return (
<div>
{products.map(product => (
<button key={product.id} onClick={() => viewProduct(product)}>
{product.name}
</button>
))}
</div>
);
}
// Access state with useLocation
import { useLocation } from 'react-router-dom';
function ProductDetail() {
const location = useLocation();
const product = location.state?.product;
return <div>{product?.name}</div>;
}
π Protected Routes
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children }) {
const { user } = useAuth(); // Your auth context
if (!user) {
// Redirect to login if not authenticated
return <Navigate to="/login" replace />;
}
return children;
}
// Usage
function App() {
return (
<Routes>
<Route path="/login" element={<Login />} />
{/* Protected routes */}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
</Routes>
);
}
// Admin-only route
function AdminRoute({ children }) {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" />;
}
if (user.role !== 'admin') {
return <Navigate to="/unauthorized" />;
}
return children;
}
ποΈ Nested Routes & Layout
import { Routes, Route, Outlet } from 'react-router-dom';
// Layout component
function DashboardLayout() {
return (
<div className="dashboard">
<aside>
<nav>
<Link to="/dashboard">Overview</Link>
<Link to="/dashboard/products">Products</Link>
<Link to="/dashboard/orders">Orders</Link>
<Link to="/dashboard/settings">Settings</Link>
</nav>
</aside>
<main>
{/* Nested routes render here */}
<Outlet />
</main>
</div>
);
}
// App with nested routes
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
{/* Parent route with layout */}
<Route path="/dashboard" element={<DashboardLayout />}>
{/* Child routes */}
<Route index element={<DashboardOverview />} />
<Route path="products" element={<Products />} />
<Route path="orders" element={<Orders />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
// URLs:
// /dashboard β DashboardLayout + DashboardOverview
// /dashboard/products β DashboardLayout + Products
// /dashboard/orders β DashboardLayout + Orders
β 404 Page
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* Catch all unmatched routes */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}
function NotFound() {
const navigate = useNavigate();
return (
<div className="not-found">
<h1>404 - Page Not Found</h1>
<p>The page you're looking for doesn't exist.</p>
<button onClick={() => navigate('/')}>Go Home</button>
</div>
);
}
π― Complete Example: E-commerce App
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Navbar from './components/Navbar';
import Home from './pages/Home';
import Products from './pages/Products';
import ProductDetail from './pages/ProductDetail';
import Cart from './pages/Cart';
import Checkout from './pages/Checkout';
import Login from './pages/Login';
import NotFound from './pages/NotFound';
function App() {
return (
<div>
<Navbar />
<Routes>
{/* Public routes */}
<Route path="/" element={<Home />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/login" element={<Login />} />
{/* Protected routes */}
<Route path="/cart" element={<ProtectedRoute><Cart /></ProtectedRoute>} />
<Route path="/checkout" element={<ProtectedRoute><Checkout /></ProtectedRoute>} />
{/* 404 */}
<Route path="*" element={<NotFound />} />
</Routes>
</div>
);
}
// Navbar.jsx
import { Link, NavLink } from 'react-router-dom';
import { useCart } from '../context/CartContext';
import { useAuth } from '../context/AuthContext';
function Navbar() {
const { itemCount } = useCart();
const { user, logout } = useAuth();
return (
<nav className="navbar">
<Link to="/" className="logo">ShopApp</Link>
<div className="nav-links">
<NavLink to="/">Home</NavLink>
<NavLink to="/products">Products</NavLink>
{user ? (
<>
<NavLink to="/cart">
Cart ({itemCount})
</NavLink>
<button onClick={logout}>Logout</button>
</>
) : (
<NavLink to="/login">Login</NavLink>
)}
</div>
</nav>
);
}
// Products.jsx
import { useSearchParams } from 'react-router-dom';
function Products() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category') || 'all';
const sort = searchParams.get('sort') || 'name';
return (
<div>
<h1>Products</h1>
<select
value={category}
onChange={e => setSearchParams({ category: e.target.value, sort })}
>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="clothing">Clothing</option>
</select>
{/* Product list */}
</div>
);
}
// ProductDetail.jsx
import { useParams, useNavigate } from 'react-router-dom';
function ProductDetail() {
const { id } = useParams();
const navigate = useNavigate();
const [product, setProduct] = useState(null);
useEffect(() => {
fetch(`/api/products/${id}`)
.then(res => res.json())
.then(setProduct)
.catch(() => navigate('/products'));
}, [id, navigate]);
if (!product) return <div>Loading...</div>;
return (
<div>
<button onClick={() => navigate(-1)}>β Back</button>
<h1>{product.name}</h1>
<p>${product.price}</p>
<button>Add to Cart</button>
</div>
);
}
π― Key Takeaways
- BrowserRouter: Wrap app for routing
- Routes & Route: Define pages/paths
- Link/NavLink: Navigation without reload
- useParams: Access URL parameters
- useSearchParams: Query string parameters
- useNavigate: Programmatic navigation
- Nested routes: Layouts with Outlet
- Protected routes: Authentication checks