React Router for Multi-Page Apps
Bulletin has multiple pages: a feed, post detail, user profiles, login, and register. React Router makes navigation work without full page reloads.
Installing React Router
Section titled “Installing React Router”npm install react-router-domThe Bulletin route structure
Section titled “The Bulletin route structure”/ → PostFeed (public)/posts/:id → PostDetail (public)/users/:id → UserProfile (public)/login → LoginPage (redirect if authed)/register → RegisterPage (redirect if authed)/create → CreatePost (requires auth)Setting up routes
Section titled “Setting up routes”import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'import { AuthProvider } from './context/AuthContext'import Layout from './components/Layout'import PostFeed from './pages/PostFeed'import PostDetail from './pages/PostDetail'import UserProfile from './pages/UserProfile'import LoginPage from './pages/LoginPage'import RegisterPage from './pages/RegisterPage'import CreatePost from './pages/CreatePost'import ProtectedRoute from './components/ProtectedRoute'
function App() { return ( <BrowserRouter> <AuthProvider> <Routes> <Route element={<Layout />}> <Route path="/" element={<PostFeed />} /> <Route path="/posts/:id" element={<PostDetail />} /> <Route path="/users/:id" element={<UserProfile />} /> <Route path="/login" element={<LoginPage />} /> <Route path="/register" element={<RegisterPage />} /> <Route path="/create" element={ <ProtectedRoute> <CreatePost /> </ProtectedRoute> } /> </Route> </Routes> </AuthProvider> </BrowserRouter> )}The Layout component
Section titled “The Layout component”<Outlet /> renders the matched child route:
import { Outlet } from 'react-router-dom'import Navbar from './Navbar'
export default function Layout() { return ( <> <Navbar /> <main className="container"> <Outlet /> </main> </> )}Navigation with useNavigate
Section titled “Navigation with useNavigate”import { useNavigate } from 'react-router-dom'
function CreatePostForm() { const navigate = useNavigate()
async function handleSubmit() { const { data } = await postsApi.create({ title, body }) navigate(`/posts/${data.id}`) // Go to the new post }}Reading route params
Section titled “Reading route params”import { useParams } from 'react-router-dom'
function PostDetail() { const { id } = useParams<{ id: string }>() const postId = parseInt(id!)
const { data, loading, error } = useApi(() => postsApi.get(postId)) // ...}import { Link, NavLink } from 'react-router-dom'
// Regular link<Link to={`/posts/${post.id}`}>{post.title}</Link>
// NavLink — adds 'active' class when the route matches<NavLink to="/" end>Home</NavLink>Exercise
Section titled “Exercise”- Install
react-router-dom. - Set up the
App.tsxwith all routes. - Create stub pages for each route (just an
<h1>heading). - Create the
Layoutcomponent withNavbarand<Outlet />. - Navigate between pages and verify routing works.
BrowserRouter+Routes+Routeset up client-side routing.- Nested routes share layout —
<Outlet />renders the active child. useNavigate()for programmatic navigation;Link/NavLinkfor declarative links.useParams()reads dynamic URL segments like:id.- Wrap protected routes in a
ProtectedRoutecomponent (built in Module 09).