You've learned the basics of React Router - now let's dive deeper! You'll learn how to navigate programmatically (from JavaScript code), create nested routes for complex layouts, use index routes for defaults, and build sophisticated navigation patterns. These features let you build real-world applications with complex routing requirements. Let's level up your routing skills! 🎯
Programmatic Navigation
Sometimes you need to navigate from code (not from a link click):
The useNavigate Hook
import { useNavigate } from 'react-router-dom'
function LoginForm() {
const navigate = useNavigate()
const [email, setEmail] = React.useState('')
const [password, setPassword] = React.useState('')
const handleSubmit = async (e) => {
e.preventDefault()
try {
await loginUser(email, password)
// Navigate to dashboard after successful login
navigate('/dashboard')
} catch (error) {
console.error('Login failed:', error)
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
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>
)
}
// Common use cases for programmatic navigation:
// - After form submission
// - After successful authentication
// - Redirecting after timeout
// - Based on user role/permissions
// - After data is savedNavigate with Options
import { useNavigate } from 'react-router-dom'
function Component() {
const navigate = useNavigate()
// Basic navigation
navigate('/about')
// Navigate with replace (doesn't add to history)
navigate('/login', { replace: true })
// User can't go "back" to previous page
// Navigate with state (pass data to next page)
navigate('/profile', {
state: { from: 'dashboard', userId: 123 }
})
// Navigate back
navigate(-1) // Go back one page
navigate(-2) // Go back two pages
// Navigate forward
navigate(1) // Go forward one page
return <button onClick={() => navigate('/home')}>Go Home</button>
}
// Options explained:
// replace: true - Replaces current entry in history (can't go back)
// state: { ... } - Pass data to the destination routeuseNavigate vs Link:
Use Link when:
• User clicks to navigate
• Navigation is the primary action
• You want a link (a tag) in HTML
Use useNavigate when:
• Navigation happens after logic (form submit, API call)
• Conditional navigation
• Programmatic redirects
• Need to navigate from event handlers
Nested Routes
Nested routes let you build layouts with sections that change while keeping other parts the same:
Basic Nested Routes
import { Routes, Route, Outlet, Link } from 'react-router-dom'
// Layout component with nested routes
function Dashboard() {
return (
<div className="dashboard">
<h1>Dashboard</h1>
<nav>
<Link to="/dashboard/profile">Profile</Link>
<Link to="/dashboard/settings">Settings</Link>
<Link to="/dashboard/stats">Stats</Link>
</nav>
{/* Outlet renders the matched child route */}
<div className="dashboard-content">
<Outlet />
</div>
</div>
)
}
function Profile() {
return <div>Profile Content</div>
}
function Settings() {
return <div>Settings Content</div>
}
function Stats() {
return <div>Stats Content</div>
}
// Route configuration
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
{/* Parent route */}
<Route path="/dashboard" element={<Dashboard />}>
{/* Child routes */}
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
<Route path="stats" element={<Stats />} />
</Route>
</Routes>
)
}
// URLs:
// /dashboard → Dashboard (no child)
// /dashboard/profile → Dashboard + Profile
// /dashboard/settings → Dashboard + Settings
// /dashboard/stats → Dashboard + StatsThe Outlet Component
import { Outlet } from 'react-router-dom'
function Layout() {
return (
<div>
<header>Header (always visible)</header>
{/* Child routes render here */}
<main>
<Outlet />
</main>
<footer>Footer (always visible)</footer>
</div>
)
}
// Outlet acts as a placeholder for child routes
// It's like {children} but for nested routes
// Example flow:
// URL: /dashboard/profile
// 1. Dashboard component renders
// 2. Dashboard's <Outlet /> is replaced with Profile component
// 3. Result: Dashboard wrapper + Profile contentNested Routes with Shared Layout
import { Routes, Route, Outlet, NavLink } from 'react-router-dom'
// Shared layout for all dashboard pages
function DashboardLayout() {
return (
<div className="dashboard-layout">
<aside className="sidebar">
<h2>Dashboard</h2>
<nav>
<NavLink to="/dashboard">Overview</NavLink>
<NavLink to="/dashboard/users">Users</NavLink>
<NavLink to="/dashboard/products">Products</NavLink>
<NavLink to="/dashboard/orders">Orders</NavLink>
</nav>
</aside>
<main className="dashboard-main">
{/* Different pages render here */}
<Outlet />
</main>
</div>
)
}
// Page components
function Overview() {
return <h1>Dashboard Overview</h1>
}
function Users() {
return <h1>Users Management</h1>
}
function Products() {
return <h1>Products List</h1>
}
function Orders() {
return <h1>Orders History</h1>
}
// Routes configuration
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
{/* Dashboard with nested routes */}
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<Overview />} />
<Route path="users" element={<Users />} />
<Route path="products" element={<Products />} />
<Route path="orders" element={<Orders />} />
</Route>
</Routes>
)
}
// Benefits:
// - Sidebar stays visible on all dashboard pages
// - Only main content changes
// - Navigation state preserved
// - Cleaner code organizationIndex Routes
Index routes define the default child route when no child path is specified:
import { Routes, Route, Outlet, Link } from 'react-router-dom'
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<nav>
<Link to="/dashboard">Home</Link>
<Link to="/dashboard/profile">Profile</Link>
<Link to="/dashboard/settings">Settings</Link>
</nav>
<Outlet />
</div>
)
}
function DashboardHome() {
return <div>Welcome to Dashboard Home!</div>
}
function App() {
return (
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
{/* Index route - renders at /dashboard */}
<Route index element={<DashboardHome />} />
{/* Regular nested routes */}
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
)
}
// URLs:
// /dashboard → Dashboard + DashboardHome (index route)
// /dashboard/profile → Dashboard + Profile
// /dashboard/settings → Dashboard + Settings
// Without index route:
// /dashboard would show Dashboard but empty Outlet
// With index route:
// /dashboard shows Dashboard + DashboardHome (better UX!)💡 Index Route Use Cases
Use index routes for:
• Default content in layouts
• Welcome/overview pages
• Empty state handling
• Dashboard home pages
Think of index routes as the "home page" of a section!
Route Matching and Priority
Route Order Matters (Sometimes)
// React Router v6 uses ranked route matching
// More specific routes match first, regardless of order!
function App() {
return (
<Routes>
{/* These routes can be in any order */}
<Route path="/users" element={<Users />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="/users/new" element={<NewUser />} />
<Route path="*" element={<NotFound />} />
</Routes>
)
}
// Matching logic:
// /users/new → NewUser (most specific - exact match)
// /users/123 → UserDetail (dynamic parameter)
// /users → Users (exact path)
// /anything → NotFound (catch-all)
// React Router v6 automatically ranks routes by specificity:
// 1. Static paths (exact matches)
// 2. Dynamic segments (:id)
// 3. Catch-all routes (*)Relative vs Absolute Paths
function App() {
return (
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
{/* Relative paths (no leading slash) */}
<Route path="profile" element={<Profile />} />
{/* Matches: /dashboard/profile */}
<Route path="settings" element={<Settings />} />
{/* Matches: /dashboard/settings */}
{/* Absolute path (with leading slash) */}
<Route path="/admin" element={<Admin />} />
{/* Matches: /admin (NOT /dashboard/admin) */}
</Route>
</Routes>
)
}
// Rule:
// - Relative paths (no /) build on parent path
// - Absolute paths (with /) start from root
// In nested routes, usually use relative paths!Interactive Demo
💡 Tip: Edit the code above and click "Run" to see your changes
Navigation with State
Pass data between routes using navigation state:
import { useNavigate, useLocation } from 'react-router-dom'
// Sending component
function UserList() {
const navigate = useNavigate()
const handleUserClick = (user) => {
// Navigate with state
navigate('/user-details', {
state: {
user: user,
from: 'user-list'
}
})
}
return (
<div>
<button onClick={() => handleUserClick({ id: 1, name: 'Alice' })}>
View Alice
</button>
</div>
)
}
// Receiving component
function UserDetails() {
const location = useLocation()
// Access state from navigation
const { user, from } = location.state || {}
if (!user) {
return <div>No user data</div>
}
return (
<div>
<h1>User Details</h1>
<p>Name: {user.name}</p>
<p>ID: {user.id}</p>
<p>Came from: {from}</p>
</div>
)
}
// Use cases:
// - Pass form data between steps
// - Send selected item details
// - Track navigation history
// - Pass temporary data (not in URL)Protected Routes Pattern
Redirect unauthorized users away from protected pages:
import { Navigate, useLocation } from 'react-router-dom'
// Protected Route component
function ProtectedRoute({ children }) {
const { user } = useAuth() // Your auth hook
const location = useLocation()
if (!user) {
// Redirect to login, save where they were trying to go
return <Navigate to="/login" state={{ from: location }} replace />
}
return children
}
// Usage in routes
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
{/* Protected routes */}
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
</Routes>
)
}
// Login component can redirect back
function Login() {
const navigate = useNavigate()
const location = useLocation()
const from = location.state?.from?.pathname || '/'
const handleLogin = async (credentials) => {
await loginUser(credentials)
navigate(from, { replace: true }) // Go back to where they were
}
return <LoginForm onSubmit={handleLogin} />
}Routes and Navigation Best Practices
- Use nested routes for layouts - Keep common UI visible
- Use index routes for defaults - Better UX
- Use relative paths in nested routes - Easier to maintain
- Use useNavigate for programmatic navigation - After actions
- Use Link/NavLink for user clicks - Better accessibility
- Add loading states - Show feedback during navigation
- Handle 404s with catch-all route - path="*"
- Use replace option carefully - Prevents going back
- Protect private routes - Check authentication
- Keep route structure logical - Match URL to app structure
- Use navigation state sparingly - URL params are better for shareable data
Routing Architecture Tips:
Shallow nesting: Keep routes 2-3 levels deep maximum
Layouts: Use nested routes for shared layouts
Code splitting: Lazy load routes for better performance
SEO: Use meaningful URLs for better indexing
Analytics: Track route changes for user behavior
Key Takeaways
useNavigateenables programmatic navigation- Navigate with options: replace, state
- Nested routes create layouts with
<Outlet> <Outlet>is a placeholder for child routes- Index routes provide default content
- React Router v6 uses ranked route matching
- Relative paths build on parent path
- Absolute paths (with /) start from root
- Pass data with navigation state
- Access location state with
useLocation - Protect routes with conditional rendering
- Use
<Navigate>for redirects - Nested routes keep common UI visible
What's Next?
You now understand advanced routing with React Router! You know how to navigate programmatically, create nested routes, and build complex layouts.
In the next lesson, you'll learn about Dynamic Routes and Parameters:
- URL parameters with :id syntax
- Accessing route parameters with useParams
- Query strings and search params
- Optional parameters
- Building dynamic route structures
Get ready to build routes that handle dynamic data! 🚀