React Router Tutorial: Navigation, Routes, and Dynamic Routing

React Router is a powerful JavaScript library that enables client-side routing for React applications. It allows you to build single-page applications with multiple views, URL navigation, and bookmarkable states without full page reloads.

React Router v6 (the current version) provides a modern, declarative approach to routing with improved performance, smaller bundle size, and a cleaner API compared to previous versions.

Why React Router?
React Router enables: dynamic route matching, lazy component loading, location transition handling, dynamic route generation, and state preservation during navigation. It’s the industry standard for React routing.

Installation and Setup

Install React Router

npm install react-router-dom

Basic App Setup

Wrap your application with BrowserRouter to enable routing:

// main.jsx
import { BrowserRouter } from ‘react-router-dom’;
import App from ‘./App’;
import ‘./index.css’;export default function Root() {
return (
<BrowserRouter>
<App />
</BrowserRouter>
);
}

Route Configuration

// App.jsx
import { Routes, Route } from ‘react-router-dom’;
import Home from ‘./pages/Home’;
import About from ‘./pages/About’;
import NotFound from ‘./pages/NotFound’;export default function App() {
return (
<Routes>
<Route path=”/” element={<Home />} />
<Route path=”/about” element={<About />} />
<Route path=”*” element={<NotFound />} />
</Routes>
);
}

Basic Routing with Routes

Understanding Routes and Route

The Routes component renders the first Route that matches the current URL. Each Route defines a path and the component to render.

Component Purpose Example
<Routes> Container for all routes <Routes>…</Routes>
<Route> Define a single route <Route path=”/” element={<Home />} />
path URL pattern to match “/”, “/about”, “/posts/:id”
element Component to render element={<Home />}

Route Matching Order

<Routes>
<Route path=”/posts/:id” element={<PostDetail />} />
<Route path=”/posts/new” element={<NewPost />} />
<Route path=”/posts” element={<PostList />} />
<Route path=”*” element={<NotFound />} />
</Routes>
⚠️ Route Order Matters: In React Router v6, routes don’t have to be in order, but more specific paths should come before less specific ones for clarity. Always put the wildcard “*” route last.

Dynamic Routes with Parameters

Creating Dynamic Routes

Use colon syntax to create URL parameters:

// App.jsx
<Route path=”/posts/:id” element={<PostDetail />} />
<Route path=”/users/:userId/posts/:postId” element={<UserPost />} />

Accessing Route Parameters with useParams

import { useParams } from ‘react-router-dom’;export default function PostDetail() {
const { id } = useParams();return (
<div>
<h1>Post {id}</h1>
<p>This is the detail page for post {id}</p>
</div>
);
}

Multiple Parameters Example

// Route: /users/123/posts/456
export default function UserPost() {
const { userId, postId } = useParams();return (
<div>
<h1>User {userId}’s Post {postId}</h1>
<Link to={`/users/${userId}`}>
Back to User
</Link>
</div>
);
}

Query Parameters with useSearchParams

import { useSearchParams } from ‘react-router-dom’;// URL: /posts?sort=date&category=react
export default function PostList() {
const [searchParams, setSearchParams] = useSearchParams();const sort = searchParams.get(‘sort’);
const category = searchParams.get(‘category’);

return (
<div>
<p>Sort: {sort}, Category: {category}</p>
<button onClick={() => setSearchParams({ sort: ‘price’ })}>
Sort by Price
</button>
</div>
);
}

Nested Routes and Layouts

Creating Nested Routes

Organize related routes under a parent route:

export default function App() {
return (
<Routes>
<Route path=”/” element={<Home />} />
<Route path=”/posts” element={<PostLayout />}>
<Route index element={<PostList />} />
<Route path=”:id” element={<PostDetail />} />
<Route path=”new” element={<NewPost />} />
</Route>
</Routes>
);
}

Layout Component with Outlet

Use Outlet to render child routes:

import { Outlet, Link } from ‘react-router-dom’;export default function PostLayout() {
return (
<div className=”post-layout”>
<aside className=”sidebar”>
<Link to=”/posts”>All Posts</Link>
<Link to=”/posts/new”>New Post</Link>
</aside>
<main>
<Outlet />
</main>
</div>
);
}
Outlet Component: The <Outlet> component renders the matched child route. It’s essential for layouts that wrap multiple child routes.

Essential Router Hooks

useNavigate for Programmatic Navigation

Navigate programmatically after an action:

import { useNavigate } from ‘react-router-dom’;export default function CreatePost() {
const navigate = useNavigate();const handleSubmit = async (e) => {
e.preventDefault();
// Save post…
navigate(‘/posts’);
// Or go back:
// navigate(-1);
};

return <form onSubmit={handleSubmit}>…</form>;
}

useLocation for Current URL

import { useLocation } from ‘react-router-dom’;export default function Breadcrumbs() {
const location = useLocation();return (
<div>
<p>Current path: {location.pathname}</p>
<p>Search: {location.search}</p>
</div>
);
}

useMatch and useResolvedPath

import { useMatch, useResolvedPath } from ‘react-router-dom’;export default function ConditionalNav() {
const postMatch = useMatch(‘/posts/:id’);return (
<div>
{postMatch && <p>You are viewing post {postMatch.params.id}</p>}
</div>
);
}

Hook Purpose Returns
useNavigate() Programmatic navigation Function to navigate
useParams() Get URL parameters Object with route params
useLocation() Get current location info Location object
useSearchParams() Get/set query parameters [params, setParams]
useMatch() Check if pattern matches Match object or null

Advanced Routing Patterns

Protected Routes

Create private routes that require authentication:

function ProtectedRoute({ isAuthenticated, children }) {
if (!isAuthenticated) {
return <Navigate to=”/login” replace />;
}
return children;
}// Usage:
<Route
path=”/dashboard”
element={
<ProtectedRoute isAuthenticated={user !== null}>
<Dashboard />
</ProtectedRoute>
}
/>

Lazy Loading Routes

Code split routes for better performance:

import { lazy, Suspense } from ‘react’;const HeavyComponent = lazy(() =>
import(‘./pages/HeavyComponent’)
);<Route
path=”/heavy”
element={
<Suspense fallback={<div>Loading…</div>}>
<HeavyComponent />
</Suspense>
}
/>

Redirects and Navigate

import { Navigate } from ‘react-router-dom’;// Redirect old routes to new ones
<Route path=”/old-about” element={<Navigate to=”/about” replace />} />// Or programmatically:
navigate(‘/new-path’, { replace: true });

Handling 404 Pages

export default function NotFound() {
return (
<div>
<h1>404 – Page Not Found</h1>
<p>The page you are looking for does not exist.</p>
<Link to=”/”>Go Home</Link>
</div>
);
}// In Routes (must be last):
<Route path=”*” element={<NotFound />} />

Best Practices

1. Keep Routes Organized

Create a separate file for route configuration:

// routes.jsx
import Home from ‘./pages/Home’;
import About from ‘./pages/About’;
import NotFound from ‘./pages/NotFound’;export const routes = [
{ path: ‘/’, element: <Home /> },
{ path: ‘/about’, element: <About /> },
{ path: ‘*’, element: <NotFound /> },
];

2. Use Relative Paths in Nested Routes

// More maintainable with relative paths
<Route path=”posts” element={<PostLayout />}>
<Route index element={<PostList />} />
<Route path=”:id” element={<PostDetail />} />
<Route path=”new” element={<NewPost />} />
</Route>

3. Handle Loading States

export default function PostDetail() {
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const { id } = useParams();useEffect(() => {
fetchPost(id).then(setPost).finally(() => setLoading(false));
}, [id]);if (loading) return <div>Loading…</div>;
if (!post) return <NotFound />;
return <article>{post.title}</article>;
}

4. Preserve Scroll Position

import { useEffect } from ‘react’;
import { useLocation } from ‘react-router-dom’;export function ScrollToTop() {
const { pathname } = useLocation();useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);

return null;
}

// Add to App:
<BrowserRouter>
<ScrollToTop />
<Routes>…</Routes>
</BrowserRouter>

© 2025 JavaScript UX. React Router Tutorial.

Leave a Reply

Your email address will not be published. Required fields are marked *