Class 18: React Router
Traditional websites rely on server-side routing, where each URL change results in a full page reload. Modern web applications often use a Single Page Application (SPA) architecture, where the entire application loads once, and subsequent "page" navigations happen client-side without full reloads. React Router is the most popular library for handling client-side routing in React applications.
React Router: Client-Side Routing
In traditional multi-page applications, clicking a link causes the browser to request a new HTML page from the server, leading to a full page refresh. In contrast, Single-Page Applications (SPAs) load most of the necessary code (HTML, CSS, JavaScript) upfront, and then dynamically update the DOM with new data from the server, without the browser reloading entire new pages. This results in faster and more fluid user experience.
React Router is the standard library for client-side routing in React. It allows you to:
- Define different routes for your application.
- Render specific components based on the current URL.
- Handle navigation without full page reloads.
- Manage URL parameters and query strings.
Core Components of React Router
Key components you'll typically use from
react-router-dom
:
-
BrowserRouter
: The router that uses the HTML5 History API (pushState, replaceState, and the popstate event) to keep your UI in sync with the URL. It's usually the root of your routing setup. -
Routes
: A container component that groups individualRoute
components. It looks through its childrenRoute
and renders the first one that matches the current URL. -
Route
: Defines a mapping between a URL path and the component that should be rendered when that path is matched. It takes apath
prop and anelement
prop. -
Link
: A component that renders an accessible anchor tag (<a>
). It's used for navigation within your application without causing a full page reload. It takes ato
prop which specifies the destination path.
Installation
First, you need to install react-router-dom
in your
React project:
npm install react-router-dom
The npm install
command downloads the
react-router-dom
package and its dependencies from the npm registry and adds them
to your project's node_modules
directory, making
them available for use in your React application.
Nested Routes
React Router also supports nested routes, allowing you to define child routes that render within their parent components. This is particularly useful for creating layouts where parts of the UI remain consistent while sub-sections change.
The <Outlet>
component is used in parent
route elements to render their child route elements. This means
that when a parent route matches, its element is rendered, and
then the child route's element is rendered inside wherever the
<Outlet>
is placed.
// src/components/Dashboard.jsx
import { Link, Outlet } from 'react-router-dom';
function Dashboard() {
return (
<div>
<h2>User Dashboard</h2>
<nav>
<ul>
<li>
<Link to="/dashboard/profile">Profile</Link>
</li>
<li>
<Link to="/dashboard/settings">Settings</Link>
</li>
</ul>
</nav>
{/* The Outlet renders the matched child route component */}
<div>
<Outlet />
</div>
</div>
);
}
export default Dashboard;
// src/App.jsx
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
import Dashboard from "./components/Dashboard";
function Home() {
return <h2>Welcome to the Home Page!</h2>;
}
function About() {
return <h2>About Us</h2>;
}
function ProfileSettings() {
return <h3>User Profile Settings</h3>;
}
function AccountSettings() {
return <h3>Account Settings</h3>;
}
function App() {
return (
<Router>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/dashboard">Dashboard</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />}>
<Route path="profile" element={<ProfileSettings />} />
<Route path="settings" element={<AccountSettings />} />
</Route>
</Routes>
</Router>
);
}
export default App;
In this setup, when you navigate to /
, the
Home
component will be rendered. When you navigate
to /about
, the About
component will be
rendered. The Link
components ensure that
navigation happens client-side without full page reloads.
When you navigate to /dashboard/profile
, the
Dashboard
component will render, and inside its
<Outlet>
, the ProfileSettings
component will be displayed.
Route Parameters
Often, you'll need routes that can accept dynamic values in the URL, such as an item ID, a user ID, or a product category. React Router allows you to define these dynamic segments as "route parameters."
You define a route parameter by prefixing a segment in your
path
with a colon (:
), for example,
/users/:id
or /products/:category/:id
.
Accessing Route Parameters with useParams
Hook
The useParams
hook from
react-router-dom
allows you to easily access these dynamic URL parameters within
your component. It returns an object of key/value pairs of the
dynamic params from the current URL that were matched by the
<Route>
.
// src/components/UserProfile.jsx
import { useParams } from 'react-router-dom';
function UserProfile() {
const { id } = useParams(); // Access the 'id' parameter from the URL
return (
<div>
<h2>User Profile</h2>
<p>Displaying profile for user ID: {id}</p>
{/* In a real app, you would fetch user data based on this ID */}
</div>
);
}
export default UserProfile;
// src/App.jsx (relevant part)
import UserProfile from "./components/UserProfile";
<Routes>
{/* ... other routes ... */}
<Route path="/users/:id" element={<UserProfile />} />
</Routes>
Now, if a user navigates to /users/123
, the
UserProfile
component will render, and the
id
variable within it will be "123"
.
Programmatic Navigation
While <Link>
components are excellent for
user-initiated navigation, there are scenarios where you need to
navigate programmatically based on certain actions or
conditions, such as after a form submission, successful API
call, or a login.
Using the useNavigate
Hook
The useNavigate
hook provides a function that lets
you navigate programmatically. When you call this function, it
pushes a new entry onto the history stack, similar to clicking a
<Link>
.
// src/components/LoginForm.jsx
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const navigate = useNavigate(); // Get the navigate function
const handleSubmit = (e) => {
e.preventDefault();
// Simulate a login API call
if (username === 'user' && password === 'password') {
alert('Login successful!');
navigate('/dashboard'); // Redirect to the dashboard page
setUsername("");
setPassword("");
} else {
alert('Invalid credentials');
}
};
return (
<div>
<h2>Login</h2>
<form onSubmit={handleSubmit}>
<div>
<label>Username:</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
/>
</div>
<div>
<label>Password:</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
<button type="submit">Login</button>
</form>
</div>
);
}
export default LoginForm;
In this example, after a simulated successful login,
navigate('/dashboard')
is called to send the user
to the /dashboard
route. You can also pass a second
argument to navigate
for options like
replace: true
to replace the current entry in the
history stack instead of pushing a new one, useful for
preventing the user from navigating back to a login page after
successful authentication.
Not Found (404) Route
In any web application, it's important to gracefully handle URLs that do not match any defined routes. This is commonly known as a "404 Not Found" page. React Router allows you to set up a catch-all route that renders a specific component when no other routes match the current URL.
Catch-all Route using path="*"
You can define a "catch-all" route by using
path="*"
. This route should typically be placed as
the last <Route>
in your
<Routes>
component, as
<Routes>
renders the first matching
route.
// src/components/NotFound.jsx
function NotFound() {
return (
<div>
<h2>404 - Page Not Found</h2>
<p>Sorry, the page you are looking for does not exist.</p>
</div>
);
}
export default NotFound;
// src/App.jsx (relevant part)
import NotFound from "./components/NotFound";
<Routes>
{/* ... other routes ... */}
<Route path="*" element={<NotFound />} /> {/* Catch-all route */}
</Routes>
With this setup, if a user tries to access a URL like
/non-existent-page
, the
NotFound
component will be rendered, providing a
clear message that the page was not found.