React Router : Basics to Advanced Ultimate guide
24 April 2025 | Category: React Js
Welcome to this beginner-friendly tutorial on React Router! React Router is a powerful library for adding client-side routing to React applications, enabling navigation between different views without full page reloads. In this guide, you’ll learn how to set up React Router, define routes, navigate between pages, handle dynamic routes, and protect routes. We’ll apply these concepts by building a Blog App with a homepage, blog post list, individual post pages, and a protected dashboard for adding posts. By the end, you’ll be confident using React Router to create seamless, multi-page experiences in your React applications.
What is React Router?
React Router is a library that enables navigation and rendering of components based on the URL in a React single-page application (SPA). It allows you to map URLs to components, creating the illusion of multiple pages while keeping the app fast and responsive. React Router handles routing on the client side, avoiding server requests for page changes.
Why Learn React Router?
- Multi-Page Experience: Create apps with distinct views (e.g., home, about, profile) without reloading.
- Dynamic Routing: Render content based on URL parameters (e.g.,
/posts/:id
for specific posts). - Navigation: Enable seamless transitions between views with links or programmatic redirects.
- Core Skill: Routing is essential for building complex React applications, from dashboards to e-commerce sites.
Prerequisites
Before starting, you should have:
- Basic knowledge of React (components, props, state, JSX, events, conditional rendering, lists, forms) and JavaScript (ES6).
- Node.js and npm installed (download from nodejs.org).
- A code editor like Visual Studio Code.
- A terminal for running commands.
We’ll use Create React App to set up our project, React Router v6 (the latest version as of April 2025), and Tailwind CSS for responsive, attractive styling, consistent with your preference for visually appealing designs.
Key React Router Concepts
Let’s explore the core concepts for using React Router.
1. Setting Up React Router
Install react-router-dom
and wrap your app in a BrowserRouter
to enable routing.
Example:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const App = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
};
2. Defining Routes
Use Routes
and Route
components to map URLs to components. The path
prop defines the URL pattern, and element
specifies the component to render.
Example:
<Route path="/contact" element={<Contact />} />
3. Navigation
Use Link
for declarative navigation (like <a>
tags) and useNavigate
for programmatic navigation.
Example:
import { Link, useNavigate } from 'react-router-dom';
const Nav = () => {
const navigate = useNavigate();
return (
<div>
<Link to="/">Home</Link>
<button onClick={() => navigate('/about')}>Go to About</button>
</div>
);
};
4. Dynamic Routes
Use URL parameters (e.g., :id
) to render components dynamically based on the URL.
Example:
<Route path="/post/:id" element={<Post />} />
const Post = () => {
const { id } = useParams();
return <h2>Post ID: {id}</h2>;
};
5. Protected Routes
Create custom route components to restrict access based on conditions (e.g., authentication).
Example:
const ProtectedRoute = ({ children }) => {
const isAuthenticated = false; // Replace with auth logic
return isAuthenticated ? children : <Navigate to="/login" />;
};
6. Nested Routes
Define routes within routes to create hierarchical layouts, like a dashboard with sub-pages.
Example:
<Route path="/dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
</Route>
Setting Up the Project
Let’s create a React app to build our Blog App, which will use React Router for navigation and routing.
- Create a New React App:
Open your terminal and run:npx create-react-app blog-app cd blog-app
- Install Dependencies:
Installreact-router-dom
and Tailwind CSS:npm install react-router-dom npm install -D tailwindcss npx tailwindcss init
- Configure Tailwind CSS:
Updatetailwind.config.js
:/** @type {import('tailwindcss').Config} */ module.exports = { content: ['./src/**/*.{js,jsx,ts,tsx}'], theme: { extend: {} }, plugins: [] };
Updatesrc/index.css
:@tailwind base; @tailwind components; @tailwind utilities; body { font-family: Arial, sans-serif; margin: 0; background-color: #f5f5f5; }
- Start the Development Server:
npm start
This opens your app athttp://localhost:3000
. - Clean Up:
Opensrc/App.js
and replace its content with:const App = () => { return ( <div className="container mx-auto p-4"> <h1 className="text-3xl font-bold text-gray-800">Blog App</h1> </div> ); }; export default App;
Deletesrc/App.css
andsrc/logo.svg
.
Building the Blog App
Our Blog App will:
- Have a homepage (
/
), a blog post list (/posts
), individual post pages (/posts/:id
), and a protected dashboard (/dashboard
) for adding posts. - Use a navigation bar for routing between pages.
- Implement dynamic routes for viewing specific posts.
- Protect the dashboard route to simulate authentication.
- Use Tailwind CSS for a responsive, visually appealing design, aligning with your preference for attractive UI.
Step 1: Create the Navigation Component
- In
src
, create a file namedNav.js
:import { Link, NavLink } from 'react-router-dom'; const Nav = () => { return ( <nav className="bg-blue-500 p-4 mb-6"> <ul className="flex space-x-4 text-white"> <li> <NavLink to="/" className={({ isActive }) => isActive ? 'font-bold underline' : 'hover:underline' } > Home </NavLink> </li> <li> <NavLink to="/posts" className={({ isActive }) => isActive ? 'font-bold underline' : 'hover:underline' } > Posts </NavLink> </li> <li> <NavLink to="/dashboard" className={({ isActive }) => isActive ? 'font-bold underline' : 'hover:underline' } > Dashboard </NavLink> </li> </ul> </nav> ); }; export default Nav;
- Router Features:
- Uses
NavLink
for navigation with active state styling (isActive
). - Styled with Tailwind CSS for a responsive, blue navigation bar.
- Uses
- Router Features:
Step 2: Create the Home Component
- In
src
, create a file namedHome.js
:const Home = () => { return ( <div className="max-w-2xl"> <h2 className="text-2xl font-semibold text-gray-800 mb-4"> Welcome to Our Blog </h2> <p className="text-gray-600"> Explore our collection of blog posts or add your own in the dashboard. </p> </div> ); }; export default Home;
- Router Features:
- Simple static page for the root route (
/
). - Styled with Tailwind CSS for a clean, centered layout.
- Simple static page for the root route (
- Router Features:
Step 3: Create the Posts Component
- In
src
, create a file namedPosts.js
:import { Link } from 'react-router-dom'; const Posts = ({ posts }) => { return ( <div className="max-w-2xl"> <h2 className="text-2xl font-semibold text-gray-800 mb-4">Blog Posts</h2> {posts.length > 0 ? ( <ul className="space-y-4"> {posts.map((post) => ( <li key={post.id} className="bg-white p-4 rounded-lg shadow-md" > <Link to={`/posts/${post.id}`} className="text-blue-500 hover:underline" > <h3 className="text-lg font-semibold">{post.title}</h3> </Link> <p className="text-gray-600">{post.excerpt}</p> </li> ))} </ul> ) : ( <p className="text-gray-500">No posts available.</p> )} </div> ); }; export default Posts;
- Router Features:
- Renders a list of posts with
Link
components for navigation to dynamic routes (/posts/:id
). - Uses conditional rendering for empty states.
- Styled with Tailwind CSS for a responsive, card-based layout.
- Renders a list of posts with
- Router Features:
Step 4: Create the Post Component
- In
src
, create a file namedPost.js
:import { useParams, useNavigate } from 'react-router-dom'; const Post = ({ posts }) => { const { id } = useParams(); const navigate = useNavigate(); const post = posts.find((p) => p.id === id); if (!post) { return ( <div className="max-w-2xl"> <p className="text-red-500">Post not found.</p> <button onClick={() => navigate('/posts')} className="text-blue-500 hover:underline" > Back to Posts </button> </div> ); } return ( <div className="max-w-2xl"> <h2 className="text-2xl font-semibold text-gray-800 mb-4"> {post.title} </h2> <p className="text-gray-600 mb-4">{post.content}</p> <button onClick={() => navigate('/posts')} className="text-blue-500 hover:underline" > Back to Posts </button> </div> ); }; export default Post;
- Router Features:
- Uses
useParams
to access the dynamic:id
from the URL. - Uses
useNavigate
for programmatic navigation back to the posts list. - Handles missing posts with a fallback UI.
- Styled with Tailwind CSS for a clean, readable layout.
- Uses
- Router Features:
Step 5: Create the Dashboard Component
- In
src
, create a file namedDashboard.js
:const Dashboard = ({ onAddPost }) => { const [title, setTitle] = React.useState(''); const [content, setContent] = React.useState(''); const handleSubmit = (e) => { e.preventDefault(); if (title.trim() && content.trim()) { onAddPost({ id: Date.now().toString(), title, excerpt: content.slice(0, 100) + '...', content }); setTitle(''); setContent(''); } }; return ( <div className="max-w-2xl"> <h2 className="text-2xl font-semibold text-gray-800 mb-4"> Add New Post </h2> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label className="block text-gray-700 mb-1">Title</label> <input type="text" value={title} onChange={(e) => setTitle(e.target.value)} className="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Post title" /> </div> <div> <label className="block text-gray-700 mb-1">Content</label> <textarea value={content} onChange={(e) => setContent(e.target.value)} className="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="Post content" rows="5" /> </div> <button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded-md hover:bg-blue-600" > Add Post </button> </form> </div> ); }; export default Dashboard;
- Router Features:
- A form for adding new posts, accessible only via the protected
/dashboard
route. - Passes new post data to the parent via
onAddPost
. - Styled with Tailwind CSS for a responsive, form-based layout.
- A form for adding new posts, accessible only via the protected
- Router Features:
Step 6: Create the ProtectedRoute Component
- In
src
, create a file namedProtectedRoute.js
:import { Navigate } from 'react-router-dom'; const ProtectedRoute = ({ children }) => { const isAuthenticated = false; // Replace with real auth logic (e.g., token check) return isAuthenticated ? ( children ) : ( <div className="max-w-2xl"> <p className="text-red-500 mb-4"> You must be logged in to access the dashboard. </p> <NavLink to="/posts" className="text-blue-500 hover:underline"> Back to Posts </NavLink> </div> ); }; export default ProtectedRoute;
- Router Features:
- Protects the dashboard route, redirecting unauthenticated users with a message.
- Uses
Navigate
for redirection (simplified here with a message for demo purposes). - Styled with Tailwind CSS for consistency.
- Router Features:
Step 7: Update the App Component
- Update
src/App.js
to define routes and manage posts:import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Nav from './Nav'; import Home from './Home'; import Posts from './Posts'; import Post from './Post'; import Dashboard from './Dashboard'; import ProtectedRoute from './ProtectedRoute'; const App = () => { const [posts, setPosts] = React.useState([ { id: '1', title: 'First Blog Post', excerpt: 'This is the first post on our blog...', content: 'This is the first post on our blog. Welcome to our community!' }, { id: '2', title: 'React Router Basics', excerpt: 'Learn how to use React Router...', content: 'Learn how to use React Router to add navigation to your apps.' } ]); const addPost = (newPost) => { setPosts([...posts, newPost]); }; return ( <BrowserRouter> <div className="container mx-auto p-4"> <Nav /> <Routes> <Route path="/" element={<Home />} /> <Route path="/posts" element={<Posts posts={posts} />} /> <Route path="/posts/:id" element={<Post posts={posts} />} /> <Route path="/dashboard" element={ <ProtectedRoute> <Dashboard onAddPost={addPost} /> </ProtectedRoute> } /> <Route path="*" element={ <div className="max-w-2xl"> <p className="text-red-500">404: Page not found.</p> <NavLink to="/" className="text-blue-500 hover:underline"> Go Home </NavLink> </div> } /> </Routes> </div> </BrowserRouter> ); }; export default App;
- Router Features:
- Wraps the app in
BrowserRouter
and defines routes withRoutes
andRoute
. - Includes routes for home (
/
), posts (/posts
), dynamic post (/posts/:id
), and protected dashboard (/dashboard
). - Uses a catch-all route (
*
) for 404 errors. - Manages posts in state and passes them to
Posts
andPost
, withaddPost
for the dashboard. - Uses Tailwind CSS for a responsive, centered layout.
- Wraps the app in
- Router Features:
Step 8: Test the App
- Save all files and ensure the development server is running (
npm start
). - Open
http://localhost:3000
. You should see:- A navigation bar with links to Home, Posts, and Dashboard.
- The homepage at
/
with a welcome message. - A list of posts at
/posts
with clickable titles linking to/posts/:id
. - Individual post pages showing content and a back button.
- A protected dashboard at
/dashboard
showing an access denied message (sinceisAuthenticated
isfalse
). - A 404 page for invalid URLs (e.g.,
/random
).
- Try:
- Navigating between pages using the nav bar.
- Clicking a post title to view its details and using the back button.
- Accessing
/dashboard
to see the protected route message. - Visiting an invalid URL to test the 404 page.
- Checking responsiveness by resizing the browser or using mobile view in dev tools.
Understanding the Code
Let’s recap how React Router powers our Blog App:
- Setup:
BrowserRouter
enables routing, withRoutes
andRoute
mapping URLs to components. - Navigation:
Nav
usesNavLink
for styled navigation, andPost
usesuseNavigate
for programmatic redirects. - Dynamic Routes:
Post
usesuseParams
to access:id
and render specific post data. - Protected Routes:
ProtectedRoute
restricts access toDashboard
, showing a fallback UI. - Nested Structure: The app uses a flat route structure for simplicity, but
Nav
is shared across pages. - Responsive Design: Tailwind CSS ensures a mobile-friendly layout with a sticky nav and card-based posts, aligning with your preference for attractive, responsive UI.
- ES6 Features:
- Arrow functions for components and handlers.
- Destructuring (
{ id }
,posts
). - Spread operator for state updates (
[...posts, newPost]
). - Modules for component organization.
Best Practices for React Router
- Use
NavLink
for Active Styles: Apply styles to active routes withNavLink
’sisActive
:<NavLink className={({ isActive }) => isActive ? 'active' : ''}>Link</NavLink>
- Handle 404s: Include a catch-all route for invalid URLs:
<Route path="*" element={<NotFound />} />
- Keep Routes Organized: Group related routes in a logical structure, using nested routes for layouts.
- Use Stable Keys: Ensure list items in routed components have unique, stable
key
props:posts.map((post) => <li key={post.id}>...</li>)
- Optimize Protected Routes: Implement real authentication (e.g., with tokens) and redirect gracefully:
return isAuthenticated ? children : <Navigate to="/login" />;
- Test Navigation: Verify all routes and links work across devices, using browser dev tools for mobile testing.
Common React Router Pitfalls and Fixes
- Missing
BrowserRouter
:
Problem: Routes don’t work becauseBrowserRouter
is missing.
Fix: Wrap the app inBrowserRouter
:<BrowserRouter><Routes>...</Routes></BrowserRouter>
- Incorrect Path Matching:
Problem: Routes don’t render due to exact path mismatches.
Fix: Usepath
correctly and test with/
or dynamic params:<Route path="/posts/:id" element={<Post />} />
- Broken Links:
Problem: Using<a>
tags causes full page reloads.
Fix: UseLink
orNavLink
:<Link to="/posts">Posts</Link>
- Dynamic Route Errors:
Problem:useParams
returns undefined or data is missing.
Fix: Validate params and handle missing data:const { id } = useParams(); if (!data) return <p>Not found</p>;
- Unprotected Routes:
Problem: Sensitive routes are accessible without checks.
Fix: Use aProtectedRoute
component with auth logic.
React Router in Functional vs. Class Components
Since you’ve explored class components previously, here’s how routing differs:
- Functional Components: Use hooks (
useParams
,useNavigate
,useLocation
) for routing logic:const Post = () => { const { id } = useParams(); return <p>Post: {id}</p>; };
- Class Components: Use
withRouter
(in older versions) or wrap with hooks in v6, accessing props likethis.props.match.params
:class Post extends React.Component { render() { const { id } = this.props.match.params; return <p>Post: {id}</p>; } }
Our app uses functional components with hooks for simplicity and alignment with modern React, but routing concepts apply to both.
What’s Next?
You’ve built a Blog App using React Router! Here are some next steps:
- Add Features: Implement a login page to toggle
isAuthenticated
and enable dashboard access. - Learn More Techniques: Explore
Outlet
for nested routes oruseLocation
for tracking URL changes. - Enhance Styling: Add animations with Tailwind CSS or Framer Motion for route transitions.
- Build Another App: Create a portfolio site or e-commerce app with routing.
Practice Challenge
Add a “Login” page at /login
with a form that sets isAuthenticated
to true
(stored in state). Update ProtectedRoute
to redirect to /login
for unauthenticated users, preserving the intended URL for post-login redirection.
Resources
- React Router Documentation
- React Router: Getting Started
- Tailwind CSS Documentation
- MDN: ES6
- Create React App Guide
Congratulations on mastering React Router! You’re now equipped to build multi-page experiences with seamless navigation. Keep practicing and happy coding!