Navigating the React Jungle: A Deep Dive into Routing Types with Examples
Hi there! I’m Shrijith Venkatrama, founder of Hexmos. Right now, I’m building LiveAPI, a first of its kind tool for helping you automatically index API endpoints across all your repositories. LiveAPI helps you discover, understand and use APIs in large tech infrastructures with ease.
Routing in React is how you move between different views or pages in a single-page application (SPA). It’s what makes your app feel like a multi-page site without full page reloads. React doesn’t come with routing built-in, so we rely on libraries like React Router to handle it. In this post, we’ll break down the main types of routing in React, show you how they work with practical examples, and toss in some tips to make your routing smooth and maintainable. Let’s get started.
Why Routing Matters in React
Routing lets users navigate your app naturally, like flipping through a book. Without it, you’re stuck with one view, which isn’t great for complex apps. React Router is the go-to library because it’s flexible, widely used, and keeps your UI in sync with the URL. There are different ways to set up routing in React, each with its own use case—static, dynamic, nested, and more. We’ll explore these with code you can actually run.
For more on React Router’s core concepts, check the official docs.
Setting Up React Router: The Foundation
Before diving into routing types, you need to set up React Router. This involves installing the library and wrapping your app with a router component. The most common choice is BrowserRouter
, which uses HTML5 history API for clean URLs.
Here’s how to get started:
- Install React Router:
npm install react-router-dom
- Set up the basic router in your app.
import React from ‘react’;
import ReactDOM from ‘react-dom/client’;
import { BrowserRouter } from ‘react-router-dom’;
import App from ‘./App’;
const root = ReactDOM.createRoot(document.getElementById(‘root’));
root.render(
);
This code wraps your app in BrowserRouter
. All routing logic will live inside App
. Now, let’s explore the types of routing.
Static Routing: The Simplest Approach
Static routing is when you define fixed routes for your app’s pages, like /home
or /about
. It’s straightforward and great for apps with a predictable structure. You use the Route
component to map URLs to components.
Here’s an example:
import { Routes, Route } from 'react-router-dom';
function Home() {
return <h2>Home Page</h2>;
}
function About() {
return <h2>About Page</h2>;
}
function App() {
return (
<div>
<nav>
<a href="/home">Home</a> | <a href="/about">About</a>
</nav>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</div>
);
}
export default App;
// Output: Clicking "Home" shows "Home Page", clicking "About" shows "About Page".
Key Points:
- Use
Routes
to groupRoute
components. - The
path
prop defines the URL, andelement
specifies the component to render. - Static routes are great for simple apps but can get clunky with dynamic data.
Dynamic Routing: Handling Variable URLs
Dynamic routing is when parts of the URL change, like /users/123
or /products/shoes
. You use route parameters (e.g., :id
) to capture these dynamic segments. This is perfect for apps with user profiles, product pages, or anything driven by data.
Example with a user profile page:
import { Routes, Route, useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams(); // Extracts :userId from URL
return <h2>User Profile: {userId}</h2>;
}
function App() {
return (
<div>
<nav>
<a href="/user/123">User 123</a> | <a href="/user/456">User 456</a>
</nav>
<Routes>
<Route path="/user/:userId" element={<UserProfile />} />
</Routes>
</div>
);
}
export default App;
// Output: Navigating to "/user/123" shows "User Profile: 123".
Key Points:
- Use
:paramName
in thepath
to capture dynamic values. - The
useParams
hook grabs the parameter values in your component. - Great for RESTful APIs or database-driven apps.
Learn more about dynamic routing in the React Router params guide.
Nested Routing: Organizing Complex UIs
Nested routing lets you render routes within other routes, creating a hierarchy. This is useful for layouts with shared components, like a sidebar that stays visible while inner content changes. Use the Outlet
component to render child routes.
Here’s an example with a dashboard layout:
import { Routes, Route, Outlet } from 'react-router-dom';
function DashboardLayout() {
return (
<div>
<h2>Dashboard</h2>
<nav>
<a href="/dashboard/profile">Profile</a> | <a href="/dashboard/settings">Settings</a>
</nav>
<Outlet /> {/* Child routes render here */}
</div>
);
}
function Profile() {
return <h3>Profile Page</h3>;
}
function Settings() {
return <h3>Settings Page</h3>;
}
function App() {
return (
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
export default App;
// Output: Navigating to "/dashboard/profile" shows "Dashboard" and "Profile Page".
Key Points:
- Nest
Route
components inside a parentRoute
with a layout component. -
Outlet
is where child routes render. - Perfect for apps with shared layouts like admin panels.
Programmatic Routing: Navigating Without Links
Sometimes, you need to navigate programmatically—like after a form submission or button click. React Router’s useNavigate
hook lets you do this. It’s handy for redirects or dynamic navigation.
Example of redirecting after a button click:
import { Routes, Route, useNavigate } from 'react-router-dom';
function Home() {
const navigate = useNavigate();
return (
<div>
<h2>Home</h2>
<button onClick={() => navigate('/welcome')}>Go to Welcome</button>
</div>
);
}
function Welcome() {
return <h2>Welcome Page</h2>;
}
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/welcome" element={<Welcome />} />
</Routes>
);
}
export default App;
// Output: Clicking the button on "/" navigates to "/welcome" and shows "Welcome Page".
Key Points:
-
useNavigate
returns a function to trigger navigation. - Pass a path (e.g.,
/welcome
) or use relative navigation (e.g.,navigate(-1)
to go back). - Useful for form submissions or conditional redirects.
Protected Routes: Securing Your Pages
Protected routes restrict access to certain pages, like requiring a user to be logged in. You can create a custom component to handle this logic, checking conditions before rendering the route.
Here’s an example of a protected route:
import { Routes, Route, Navigate } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated, children }) {
return isAuthenticated ? children : <Navigate to="/login" />;
}
function Dashboard() {
return <h2>Dashboard (Protected)</h2>;
}
function Login() {
return <h2>Login Page</h2>;
}
function App() {
const isAuthenticated = true; // Replace with real auth logic
return (
<Routes>
<Route
path="/dashboard"
element={
<ProtectedRoute isAuthenticated={isAuthenticated}>
<Dashboard />
</ProtectedRoute>
}
/>
<Route path="/login" element={<Login />} />
</Routes>
);
}
export default App;
// Output: If isAuthenticated is true, "/dashboard" shows "Dashboard (Protected)". Otherwise, redirects to "/login".
Key Points:
- Use a wrapper component like
ProtectedRoute
to check conditions. -
Navigate
redirects users to another route. - Ideal for authentication or role-based access.
Query Parameters and Search: Handling URL Data
Query parameters (e.g., ?sort=asc
) let you pass data through the URL without defining routes. Use the useSearchParams
hook to read and update them. This is great for filters or search functionality.
Example with a product filter:
import { Routes, Route, useSearchParams } from 'react-router-dom';
function Products() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category') || 'all';
const handleFilter = () => {
setSearchParams({ category: 'electronics' });
};
return (
<div>
<h2>Products</h2>
<p>Current Category: {category}</p>
<button onClick={handleFilter}>Filter Electronics</button>
</div>
);
}
function App() {
return (
<Routes>
<Route path="/products" element={<Products />} />
</Routes>
);
}
export default App;
// Output: Shows "Current Category: all" initially. Clicking the button updates the URL to "/products?category=electronics" and shows "Current Category: electronics".
Key Points:
-
useSearchParams
works like React’suseState
for query params. - Useful for filters, pagination, or search forms.
- Doesn’t require predefined routes.
Choosing the Right Routing Type
Here’s a quick comparison to help you pick the right routing approach:
Routing Type | Use Case | Pros | Cons |
---|---|---|---|
Static Routing | Fixed pages (e.g., Home, About) | Simple, predictable | Limited for dynamic data |
Dynamic Routing | Variable URLs (e.g., user profiles) | Flexible, data-driven | Requires parameter handling |
Nested Routing | Hierarchical layouts (e.g., dashboards) | Organized, reusable layouts | Can get complex with deep nesting |
Programmatic Routing | Conditional navigation | Dynamic control | Needs careful state management |
Protected Routing | Restricted access | Secure, modular | Requires auth logic |
Query Parameters | Filters, search, pagination | No route setup needed | URL can get messy with many params |
Each type has its place. For example, use static routing for marketing sites, dynamic routing for e-comm apps, and protected routing for user dashboards. Mix and match as needed.
Putting It All Together: A Mini App Example
Let’s combine static, dynamic, and protected routing in a small app to see how they work together.
import { Routes, Route, Navigate, useParams, Link } from 'react-router-dom';
function ProtectedRoute({ isAuthenticated, children }) {
return isAuthenticated ? children : <Navigate to="/login" />;
}
function Home() {
return <h2>Home Page</h2>;
}
function UserProfile() {
const { userId } = useParams();
return <h2>User Profile: {userId}</h2>;
}
function Login() {
return <h2>Login Page</h2>;
}
function App() {
const isAuthenticated = true; // Replace with real auth logic
return (
<div>
<nav>
<Link to="/">Home</Link> | <Link to="/user/789">User 789</Link> | <Link to="/dashboard">Dashboard</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user/:userId" element={<UserProfile />} />
<Route
path="/dashboard"
element={
<ProtectedRoute isAuthenticated={isAuthenticated}>
<h2>Dashboard (Protected)</h2>
</ProtectedRoute>
}
/>
<Route path="/login" element={<Login />} />
</Routes>
</div>
);
}
export default App;
// Output:
// - "/" shows "Home Page".
// - "/user/789" shows "User Profile: 789".
// - "/dashboard" shows "Dashboard (Protected)" if isAuthenticated is true, else redirects to "/login".
This app uses static routing for the home page, dynamic routing for user profiles, and protected routing for the dashboard. Replace the isAuthenticated
boolean with real auth logic (e.g., checking a token).
Tips for Better Routing in React
Routing can get messy if you’re not careful. Here are some practical tips:
-
Use
Link
instead of<a>
for navigation to prevent page reloads. - Keep routes organized in a separate file for large apps.
-
Handle 404s with a catch-all route:
<Route path="*" element={<NotFound />} />
. - Test your routes to ensure they work as expected, especially protected ones.
- Use query params sparingly to avoid cluttered URLs.
React Router is powerful but requires planning to keep your app maintainable. Start simple with static routes, then add dynamic or nested routes as your app grows. For complex apps, consider a state management library to handle auth or data for protected routes.