Monday, March 3, 2025
React Router Explained and How to Build Multi-Page Applications in React
Posted by

Welcome to the world of React Router! Building multi-page applications (MPAs) in React can be efficiently managed using React Router, a powerful library designed for handling routing in React applications. This blog will guide you through the basics of setting up routes, navigating between components, and managing nested routes, ensuring that your application is organized and user-friendly.
What is React Router?
React Router is a standard library for routing in React applications. It enables the creation of single-page applications (SPAs) that can navigate between components without reloading the page. It is declarative and integrates seamlessly with React, making it a popular choice for developers building complex web applications.
Key Features of React Router
- Declarative routing: You declare your routes in a way that is closely aligned with the component tree.
- Nested routing: Easily handle nested routes, which means you can have routes inside other routes.
- Dynamic routing: Use dynamic segments in your routes to match different paths.
- URL parameters: Capture and use URL parameters in your components.
- Redirects: Manage redirects programmatically.
- Query parameters: Pass and extract query parameters from the URL.
Setting Up React Router
To get started with React Router, you need to install it in your React project. This can be done using npm or yarn.
Installation
You can install React Router by running the following command in your terminal:
npm install react-router-dom
Alternatively, if you are using yarn:
yarn add react-router-dom
Basic Setup
After installation, you can set up React Router in your application. Here’s a simple example to get you started.
Creating a Basic React Application
First, create a new React application if you don't have one already:
npx create-react-app my-app
cd my-app
Now, let’s set up React Router in this application.
Step 1: Import Required Components
Open your src/index.js
file and import the BrowserRouter
component from react-router-dom
. This is the component that wraps your application and provides the routing capabilities.
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root')
);
Step 2: Set Up Basic Routes
In your src/App.js
file, import the Route
and Routes
components from react-router-dom
. These components are used to define your routes.
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
function App() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</div>
);
}
export default App;
Step 3: Create the Page Components
Create the components for your pages. For this example, create three files: Home.js
, About.js
, and Contact.js
inside a pages
directory.
Home.js
function Home() {
return <h2>Home Page</h2>;
}
export default Home;
About.js
function About() {
return <h2>About Page</h2>;
}
export default About;
Contact.js
function Contact() {
return <h2>Contact Page</h2>;
}
export default Contact;
Step 4: Run Your Application
Run your application using npm start
or yarn start
and navigate between the pages using the links in the navigation menu.
Understanding Routing Concepts
Before diving deeper, it’s important to understand the basic routing concepts in React Router.
Route Component
The Route
component is used to define routes in your application. It takes a path
prop that specifies the URL path, and an element
prop that specifies the component to render when the path matches the URL.
Routes Component
The Routes
component wraps multiple Route
components. It ensures that only one Route
is rendered at a time.
Link Component
The Link
component is used to create navigation links that allow users to navigate between routes without reloading the page.
Nested Routing
Nested routing is a feature that allows you to define routes within other routes. This is particularly useful for creating complex and hierarchical structures in your application.
Setting Up Nested Routes
To illustrate nested routing, let's add a nested route to the About
page. We will create a Team
component that will be accessible at /about/team
.
Step 1: Create the Team Component
Create a Team.js
file in the pages
directory.
Team.js
function Team() {
return <h3>Team Page</h3>;
}
export default Team;
Step 2: Update the About Component
Modify the About.js
file to include nested routes.
About.js
import { Routes, Route, Link } from 'react-router-dom';
import Team from './Team';
function About() {
return (
<div>
<h2>About Page</h2>
<Link to="team">Our Team</Link>
<Routes>
<Route path="team" element={<Team />} />
</Routes>
</div>
);
}
export default About;
Now, if you navigate to /about/team
, you will see the Team
component rendered inside the About
component.
Programmatic Navigation
Sometimes, you might need to navigate between routes programmatically, rather than using Link
components. React Router provides a useNavigate
hook for this purpose.
useNavigate
Hook
Using the Let’s add a button on the Home page that navigates to the About page when clicked.
Home.js
import { useNavigate } from 'react-router-dom';
function Home() {
const navigate = useNavigate();
const handleClick = () => {
navigate('/about');
};
return (
<div>
<h2>Home Page</h2>
<button onClick={handleClick}>Go to About Page</button>
</div>
);
}
export default Home;
Handling Parameters
React Router allows you to capture and use dynamic segments in your routes, which can be accessed via URL parameters.
Defining Routes with Parameters
Let’s create a route for a blog post that includes an ID parameter.
Step 1: Define the Route with a Parameter
Modify the App.js
file to include a new route with a parameter.
App.js
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';
function App() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/post/:id" element={<BlogPost />} />
</Routes>
</div>
);
}
export default App;
Step 2: Access the Parameter in the Component
The BlogPost
component can access the id
parameter using the useParams
hook.
BlogPost.js
import { useParams } from 'react-router-dom';
function BlogPost() {
const { id } = useParams();
return (
<div>
<h2>Blog Post {id}</h2>
<p>This is the blog post with id {id}</p>
</div>
);
}
export default BlogPost;
Managing Query Parameters
Query parameters are additional details appended to the URL after a question mark (?
) and are useful for passing non-essential data.
Using Query Parameters
Let’s add a search feature to the blog posts. We will create a page that displays a list of blog posts and allows users to filter them using query parameters.
Step 1: Create a Blog Page
Create a new Blog.js
file in the pages
directory.
Blog.js
import { useSearchParams } from 'react-router-dom';
function Blog() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('query');
const blogPosts = [
{ id: 1, title: 'Introduction to React', content: 'Learn the basics of React...' },
{ id: 2, title: 'Advanced React Techniques', content: 'Explore advanced React techniques...' },
// Add more blog posts as needed
];
const filteredPosts = query
? blogPosts.filter(post => post.title.toLowerCase().includes(query.toLowerCase()))
: blogPosts;
return (
<div>
<h2>Blog Posts</h2>
<input
type="text"
value={query || ''}
placeholder="Search..."
onChange={e => setSearchParams({ query: e.target.value })}
/>
<ul>
{filteredPosts.map(post => (
<li key={post.id}>
<Link to={`/post/${post.id}`}>{post.title}</Link>
<p>{post.content}</p>
</li>
))}
</ul>
</div>
);
}
export default Blog;
Step 2: Add the Blog Route
Add the new Blog
component to the routes in App.js
.
App.js
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';
import Blog from './pages/Blog';
function App() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
<li>
<Link to="/blog">Blog</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/post/:id" element={<BlogPost />} />
<Route path="/blog" element={<Blog />} />
</Routes>
</div>
);
}
export default App;
Now, you can navigate to the /blog
page and use the search input to filter the blog posts.
Redirecting Users
Sometimes, you might need to redirect users to a different route based on certain conditions. React Router provides the Navigate
component for this purpose.
Navigate
Component
Using the Let’s create a scenario where users are redirected to the Home page if they try to access a non-existent route.
Step 1: Create a 404 Component
Create a NotFound.js
component that will be displayed when a route is not found.
NotFound.js
function NotFound() {
return <h2>404 - Page Not Found</h2>;
}
export default NotFound;
Navigate
Component
Step 2: Use the Modify the App.js
file to include a Route
that redirects to the Home page if no match is found.
App.js
import React from 'react';
import { Routes, Route, Link, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';
import Blog from './pages/Blog';
import NotFound from './pages/NotFound';
function App() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
<li>
<Link to="/blog">Blog</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/post/:id" element={<BlogPost />} />
<Route path="/blog" element={<Blog />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</div>
);
}
export default App;
In this example, the *
path captures all unmatched routes and redirects them to the Home page using the Navigate
component.
Conditional Routing
Sometimes, you might want to conditionally render routes based on certain conditions, such as user authentication.
Implementing Conditional Routing
Let’s create a protected route that only authenticated users can access. If a user tries to access this route without being authenticated, they will be redirected to the Login page.
Step 1: Create a Protected Route Component
Create a ProtectedRoute.js
file in the components
directory.
ProtectedRoute.js
import { Navigate, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }) {
const isAuthenticated = true; // This should be a real authentication check
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
export default ProtectedRoute;
Step 2: Create the Login Component
Create a Login.js
file in the pages
directory.
Login.js
function Login() {
return <h2>Login Page</h2>;
}
export default Login;
Step 3: Add the Protected Route
Modify the App.js
file to include the protected route.
App.js
import React from 'react';
import { Routes, Route, Link, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';
import Blog from './pages/Blog';
import NotFound from './pages/NotFound';
import Login from './pages/Login';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
<li>
<Link to="/blog">Blog</Link>
</li>
<li>
<Link to="/dashboard">Dashboard</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/post/:id" element={<BlogPost />} />
<Route path="/blog" element={<Blog />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<div>
<h2>Dashboard</h2>
<p>Welcome to your dashboard</p>
</div>
</ProtectedRoute>
}
/>
<Route path="/login" element={<Login />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</div>
);
}
export default App;
In this example, the Dashboard
route is wrapped with the ProtectedRoute
component. If the user is not authenticated, they will be redirected to the Login
page.
Handling Authentication State
To make the conditional routing example more realistic, let’s add a simple authentication state management using React’s useState
hook.
Step 1: Add Authentication State
Modify the App.js
file to include authentication state.
App.js
import React, { useState } from 'react';
import { Routes, Route, Link, Navigate } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import BlogPost from './pages/BlogPost';
import Blog from './pages/Blog';
import NotFound from './pages/NotFound';
import Login from './pages/Login';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
const [isAuthenticated, setIsAuthenticated] = useState(false);
return (
<div>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/about">About</Link>
</li>
<li>
<Link to="/contact">Contact</Link>
</li>
<li>
<Link to="/blog">Blog</Link>
</li>
<li>
<Link to="/dashboard">Dashboard</Link>
</li>
<li>
<Link to="/login">Login</Link>
</li>
</ul>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/post/:id" element={<BlogPost />} />
<Route path="/blog" element={<Blog />} />
<Route
path="/dashboard"
element={
<ProtectedRoute isAuthenticated={isAuthenticated}>
<div>
<h2>Dashboard</h2>
<p>Welcome to your dashboard</p>
</div>
</ProtectedRoute>
}
/>
<Route path="/login" element={<Login setIsAuthenticated={setIsAuthenticated} />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</div>
);
}
export default App;
Step 2: Update the Login Component
Modify the Login.js
file to handle the login process.
Login.js
import { useNavigate } from 'react-router-dom';
function Login({ setIsAuthenticated }) {
const navigate = useNavigate();
const handleLogin = () => {
setIsAuthenticated(true);
navigate('/');
};
return (
<div>
<h2>Login Page</h2>
<button onClick={handleLogin}>Login</button>
</div>
);
}
export default Login;
Now, when you click the Login button, the isAuthenticated
state is set to true
, and you can access the Dashboard page.
Advanced Routing Concepts
React Router offers several advanced features that can help you build more dynamic and interactive applications.
Conditional Navigation
You can conditionally navigate users based on certain conditions using the useNavigate
hook.
Example: Conditional Navigation
Let’s add a feature that redirects users to the Home page if they are already authenticated when they try to access the Login page.
Login.js
import { useNavigate, useLocation } from 'react-router-dom';
function Login({ setIsAuthenticated }) {
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || '/';
const isAlreadyAuthenticated = true; // This should be a real authentication check
if (isAlreadyAuthenticated) {
return <Navigate to={from} />;
}
const handleLogin = () => {
setIsAuthenticated(true);
navigate('/');
};
return (
<div>
<h2>Login Page</h2>
<button onClick={handleLogin}>Login</button>
</div>
);
}
export default Login;
Route Guards
Route guards are used to enforce rules before rendering a component. You can use the ProtectedRoute
component for this purpose.
Example: Route Guard
We already have a ProtectedRoute
component in place. You can extend it to include more complex logic if needed.
ProtectedRoute.js
import { Navigate, useLocation } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated, children }) {
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
export default ProtectedRoute;
Nested Routes for Nested Components
Nested routes allow you to create a hierarchical structure in your routes, where a parent route can render its children.
Example: Nested Routes
Let’s add a nested route to the About
page.
About.js
import { Routes, Route, Link } from 'react-router-dom';
import Team from './Team';
function About() {
return (
<div>
<h2>About Page</h2>
<Link to="team">Our Team</Link>
<Routes>
<Route path="team" element={<Team />} />
</Routes>
</div>
);
}
export default About;
Team.js
function Team() {
return <h3>Team Page</h3>;
}
export default Team;
Now, when you navigate to /about/team
, the Team
component will be rendered nested inside the About
component.
Query Parameters
Query parameters are used to pass additional information through the URL. They are useful for filtering and sorting data without changing the route.
Using Query Parameters
Let’s update the Blog
component to handle query parameters.
Blog.js
import { useSearchParams } from 'react-router-dom';
function Blog() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('query');
const blogPosts = [
{ id: 1, title: 'Introduction to React', content: 'Learn the basics of React...' },
{ id: 2, title: 'Advanced React Techniques', content: 'Explore advanced React techniques...' },
// Add more blog posts as needed
];
const filteredPosts = query
? blogPosts.filter(post => post.title.toLowerCase().includes(query.toLowerCase()))
: blogPosts;
return (
<div>
<h2>Blog Posts</h2>
<input
type="text"
value={query || ''}
placeholder="Search..."
onChange={e => setSearchParams({ query: e.target.value })}
/>
<ul>
{filteredPosts.map(post => (
<li key={post.id}>
<Link to={`/post/${post.id}`}>{post.title}</Link>
<p>{post.content}</p>
</li>
))}
</ul>
</div>
);
}
export default Blog;
Redirecting with Query Parameters
React Router allows you to redirect users to a new route while preserving query parameters.
Preserving Query Parameters on Redirect
Let’s modify the Login
component to redirect users to the Home page while preserving any query parameters.
Login.js
import { useNavigate, useLocation } from 'react-router-dom';
function Login({ setIsAuthenticated }) {
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || '/';
const isAlreadyAuthenticated = true; // This should be a real authentication check
if (isAlreadyAuthenticated) {
return <Navigate to={from} state={location.state} />;
}
const handleLogin = () => {
setIsAuthenticated(true);
navigate(from, { state: location.state });
};
return (
<div>
<h2>Login Page</h2>
<button onClick={handleLogin}>Login</button>
</div>
);
}
export default Login;
Summary of Features
React Router is a versatile tool that provides a wide range of features for managing routing in React applications. Here’s a quick summary of the features we covered:
Feature | Description |
---|---|
Declarative routing | Routes are declared in a way that is closely aligned with the component tree. |
Nested routing | Handles nested routes easily, allowing you to create complex and hierarchical structures. |
Dynamic routing | Use dynamic segments in routes to match different paths. |
URL parameters | Capture and use URL parameters in components. |
Redirects | Manage redirects programmatically. |
Query parameters | Pass and extract query parameters from the URL. |
Conditional routing | Render routes conditionally based on certain conditions. |
Nested components | Use nested routes to render components within other components. |
Authentication checks | Enforce authentication checks before rendering components. |
Best Practices
Here are some best practices to follow when using React Router in your React applications:
- Keep routes organized: Use nesting and grouping to keep your routes organized.
- Use dynamic routes: Use dynamic routes to avoid hardcoding paths.
- Protect sensitive routes: Use protected routes to secure sensitive components.
- Preserve query parameters: Use the
state
parameter to preserve query parameters during redirects. - Lazy loading: Use lazy loading to load components only when they are needed.
Troubleshooting
If you encounter issues while setting up React Router, here are some common solutions:
- Ensure you are using the correct version: Make sure you are using the latest version of
react-router-dom
. - Check your routes: Verify that your routes are correctly defined and that there are no typos.
- Use
element
prop: Use theelement
prop instead ofcomponent
prop for defining routes. - Check component imports: Ensure that all components are correctly imported and exported.
- Debug the browser console: Use the browser console to debug any errors or warnings.
Conclusion
React Router is an essential tool for building multi-page applications in React. It simplifies routing, navigation, and data handling, making it easier to create complex and interactive applications. By following the steps and examples in this blog, you should be well-equipped to start using React Router in your projects.
Happy coding! Check out the project on GitHub for a complete implementation.