Route Navigation
In this chapter, you will learn how to use React Router v6 to implement page navigation without refreshing, including dynamic routes and parameter reading.
Why Use Routing?
Traditional multi-page websites request a new page from the server every time you click a link, causing white-screen flickering.
Frontend RoutingIt lets JavaScript control which component is displayed when the URL changes, without sending requests to the server.
Benefits:Smooth switching, experience feels like a native app。
Core Concepts of React Router v6
| Concept | Purpose | Corresponding Code |
|---|---|---|
| Route table | Defines the mapping between URLs and components | <Routes> + <Route> |
| Route outlet | Placeholder where child routes are displayed | <Outlet /> |
| Navigation link | Click to navigate | <Link to="..."> |
| Dynamic route | Variable in the URL | /post/:id |
| Read parameters | Get the value from the dynamic route | useParams() |
Installation and Configuration
$ npm install react-router-dom@6
Create Route Configuration
Example
import { createBrowserRouter } from 'react-router-dom'
import App from '../App'
import HomePage from '../pages/HomePage'
import PostPage from '../pages/PostPage'
// createBrowserRouter creates a router instance
const router = createBrowserRouter([
{
path: '/', // App acts as the root layout
element: <App />,
children: [
{
index: true, // index: true means the default child route for the / path
element: <HomePage />
},
{
path: 'post/:id', // Dynamic route: :id matches any value
element: <PostPage />
}
]
}
])
export default router
Two router modes:createBrowserRouterProduces clean URLs (/post/1), requiring server configuration. If the deployment environment is uncertain, usecreateHashRouter, the URL contains a # (/#/post/1), and no server configuration is needed.
Register the Router in main.jsx
Example
import React from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider } from 'react-router-dom'
import router from './router'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
{/* RouterProvider injects the router into the app */}
<RouterProvider router={router} />
</React.StrictMode>
)
Use Outlet in App.jsx
Example
import { Outlet } from 'react-router-dom'
import NavBar from './components/NavBar'
import './App.css'
function App() {
return (
<div className="app">
<NavBar />
<main className="container">
{/* Outlet is the outlet for nested routes: the component matched by child routes is rendered here */}
<Outlet />
</main>
<footer className="footer">
<p>© 2024 EXAMPLE Blog. Powered by React.</p>
</footer>
</div>
)
}
export default App
Link — Declarative Navigation
LinkReplaces the traditional<a>tag and does not trigger a page refresh.
Example
function BlogCard({ id, title, summary, date, category }) {
return (
<Link to={`/post/${id}`} className="card-link">
<div className="card">
<span className="tag">{category}</span>
<h3>{title}</h3>
<p>{summary}</p>
<span className="date">{date}</span>
</div>
</Link>
)
}
Do not use
<a href="....html">— it causes the browser to request a new page from the server, and the entire React app will reinitialize. Use<Link to="...">to switch on the client side without a refresh.
useParams — Reading Route Parameters
On the detail page, you need to extract the article ID from the URL to find the corresponding article.
Example
function PostPage() {
// useParams returns an object containing the parameters in the URL
const { id } = useParams()
// Note: id is a string; if you need a number, use Number(id) to convert it
return (
<div>
<p>Current article ID:{id}</p>
</div>
)
}
useNavigate — Programmatic Navigation
In some scenarios, you need to navigate without clicking a link (e.g., after submitting a form, or navigating back to the home page when an article is not found).
Example
function PostPage() {
const navigate = useNavigate()
const article = null // Suppose the article is not found
if (!article) {
// When the article does not exist, navigate back to the home page
return (
<div>
<p>Article not found</p>
<button onClick={() => navigate('/')}>Back to home</button>
</div>
)
}
return <div>Article details...</div>
}
Vue3 vs React Router API Comparison
| Feature | Vue3 | React |
|---|---|---|
| Navigation link | <RouterLink> | <Link> |
| Route outlet | <RouterView> | <Outlet> |
| Dynamic route | /post/:id | /post/:id |
| Read parameters | useRoute().params | useParams() |
| Programmatic navigation | useRouter().push() | useNavigate() |
Hands-on: Complete Detail Page + Router Integration
Step 1: Modify BlogCard to Support Click Navigation
Example
import { Link } from 'react-router-dom'
function BlogCard({ id, title, summary, date, category }) {
return (
<Link to={`/post/${id}`} className="card-link">
<div className="card">
<span className="tag">{category}</span>
<h3>{title}</h3>
<p>{summary}</p>
<span className="date">{date}</span>
</div>
</Link>
)
}
export default BlogCard
Step 2: Create HomePage and PostPage
Example
import { useState, useMemo } from 'react'
import BlogCard from '../components/BlogCard'
import CategoryFilter from '../components/CategoryFilter'
function HomePage() {
const [articles] = useState([
{ id: 1, title: 'The Complete Guide to React for Beginners', summary: 'Learn React from Scratch', category: 'React', date: '2024-05-10',
content: '<h2>Why Learn React?</h2><p>React is currently one of the most popular frontend frameworks...</p>' },
{ id: 2, title: 'JS Async Programming Explained', summary: 'Understand Promise and async/await', category: 'JavaScript', date: '2024-05-08',
content: '<h2>What is Async?</h2><p>JS is single-threaded; asynchronous operations can keep the main thread unblocked...</p>' },
{ id: 3, title: 'CSS Grid Layout Practice', summary: 'Use Grid to Create Responsive Layouts', category: 'CSS', date: '2024-05-05',
content: '<h2>Getting Started with Grid</h2><p>Grid is a two-dimensional layout system...</p>' },
])
const [activeCategory, setActiveCategory] = useState('All')
const categories = useMemo(() => {
return ['All', ...new Set(articles.map(a => a.category))]
}, [articles])
const filteredArticles = useMemo(() => {
if (activeCategory === 'All') return articles
return articles.filter(a => a.category === activeCategory)
}, [articles, activeCategory])
return (
<div>
<h2 className="section-title">Latest Articles</h2>
<CategoryFilter
categories={categories}
activeCategory={activeCategory}
onCategoryChange={setActiveCategory}
/>
<p className="result-info">common{filteredArticles.length}article</p>
{filteredArticles.length === 0 ? (
<p className="empty-tip">No articles in this category yet</p>
) : (
<div className="article-grid">
{filteredArticles.map(article => (
<BlogCard key={article.id} {...article} />
))}
</div>
)}
</div>
)
}
export default HomePage
Example
import { useParams, Link } from 'react-router-dom'
import { useMemo } from 'react'
// Article data (will be loaded from external sources in later chapters)
const articles = [
{ id: 1, title: 'The Complete Guide to React for Beginners', category: 'React', date: '2024-05-10',
content: '<h2>Why Learn React?</h2><p>React is currently one of the most popular frontend frameworks...</p>' },
{ id: 2, title: 'JS Async Programming Explained', category: 'JavaScript', date: '2024-05-08',
content: '<h2>What is Async?</h2><p>JS is single-threaded...</p>' },
]
function PostPage() {
const { id } = useParams()
// Find the article by ID
const article = useMemo(() => {
return articles.find(a => a.id === Number(id))
}, [id])
// Article not found
if (!article) {
return (
<div className="not-found">
<h2>Article not found</h2>
<p>Article with ID{id}was not found.</p>
<Link to="/">Back to home</Link>
</div>
)
}
// Article exists
return (
<article className="post-view">
<span className="category-tag">{article.category}</span>
<h1>{article.title}</h1>
<time>{article.date}</time>
{/* dangerouslySetInnerHTML is equivalent to Vue's v-html, be aware of XSS risks */}
<div
className="content"
dangerouslySetInnerHTML={{ __html: article.content }}
/>
<Link to="/" className="back-link">← Back to home</Link>
</article>
)
}
export default PostPage
dangerouslySetInnerHTMLis the React version ofv-html. The name is deliberately long to remind you: don't use it to render user-generated content, there is an XSS security risk. The content here is written by the developers themselves, so it is safe.
Add Styles for PostPage
Example
.post-view {
max-width: 720px;
margin: 0 auto;
}
.category-tag {
display: inline-block;
padding: 4px 12px;
background: #e3f2fd;
color: #1976d2;
border-radius: 12px;
font-size: 13px;
margin-bottom: 12px;
}
.post-view h1 {
font-size: 32px;
margin-bottom: 12px;
line-height: 1.4;
}
.post-view time {
display: block;
color: #999;
font-size: 14px;
margin-bottom: 30px;
}
.content {
line-height: 1.8;
font-size: 16px;
color: #333;
}
.content h2 {
margin: 24px 0 12px;
font-size: 22px;
}
.content p {
margin-bottom: 12px;
}
.back-link {
display: inline-block;
margin-top: 40px;
color: #1976d2;
text-decoration: none;
}
.not-found {
text-align: center;
padding: 60px 0;
}
Chapter Summary
In this chapter, you learned the core of React Router v6: route table configuration (createBrowserRouter), Link navigation, Outlet nested outlet, dynamic route /post/:id, useParams to read parameters, and useNavigate for programmatic navigation.
Now the blog has a true multi-page experience — browse the list on the home page, click to enter the detail page to read the full content.
Other Extensions