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/:idfor 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-domand 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 startThis opens your app athttp://localhost:3000. - Clean Up:
Opensrc/App.jsand 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.cssandsrc/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
NavLinkfor 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
Linkcomponents 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
useParamsto access the dynamic:idfrom the URL. - Uses
useNavigatefor 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
/dashboardroute. - 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
Navigatefor 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.jsto 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
BrowserRouterand defines routes withRoutesandRoute. - 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
PostsandPost, withaddPostfor 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
/postswith clickable titles linking to/posts/:id. - Individual post pages showing content and a back button.
- A protected dashboard at
/dashboardshowing an access denied message (sinceisAuthenticatedisfalse). - 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
/dashboardto 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:
BrowserRouterenables routing, withRoutesandRoutemapping URLs to components. - Navigation:
NavusesNavLinkfor styled navigation, andPostusesuseNavigatefor programmatic redirects. - Dynamic Routes:
PostusesuseParamsto access:idand render specific post data. - Protected Routes:
ProtectedRouterestricts access toDashboard, showing a fallback UI. - Nested Structure: The app uses a flat route structure for simplicity, but
Navis 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
NavLinkfor 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
keyprops: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 becauseBrowserRouteris missing.
Fix: Wrap the app inBrowserRouter:<BrowserRouter><Routes>...</Routes></BrowserRouter> - Incorrect Path Matching:
Problem: Routes don’t render due to exact path mismatches.
Fix: Usepathcorrectly and test with/or dynamic params:<Route path="/posts/:id" element={<Post />} /> - Broken Links:
Problem: Using<a>tags causes full page reloads.
Fix: UseLinkorNavLink:<Link to="/posts">Posts</Link> - Dynamic Route Errors:
Problem:useParamsreturns 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 aProtectedRoutecomponent 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
isAuthenticatedand enable dashboard access. - Learn More Techniques: Explore
Outletfor nested routes oruseLocationfor 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!