React Router: Navigation and Routing
React Router enables client-side routing for single-page applications. This article covers routing fundamentals and advanced patterns.
Introduction
React Router provides:
- Client-side routing
- Dynamic route matching
- Nested routes
- Route parameters
- Navigation guards
Understanding routing helps you:
- Build multi-page SPAs
- Handle dynamic URLs
- Manage navigation
- Implement nested layouts
- Create complex routing logic
Basic Routing Setup
Router Configuration
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
// โ
Good: Basic routing setup
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/contact">Contact</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</BrowserRouter>
);
}
function Home() {
return <h1>Home Page</h1>;
}
function About() {
return <h1>About Page</h1>;
}
function Contact() {
return <h1>Contact Page</h1>;
}
// โ
Good: Navigation with useNavigate
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const handleLogin = async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
if (response.ok) {
navigate('/dashboard');
}
};
return (
<form onSubmit={(e) => {
e.preventDefault();
handleLogin({ email: '[email protected]', password: 'password' });
}}>
<button type="submit">Login</button>
</form>
);
}
Route Parameters
// โ
Good: Dynamic route parameters
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/users/:userId" element={<UserProfile />} />
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />
</Routes>
</BrowserRouter>
);
}
// โ
Good: Access route parameters
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams();
const [user, setUser] = React.useState(null);
React.useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]);
return <div>{user?.name}</div>;
}
function Comment() {
const { postId, commentId } = useParams();
return (
<div>
<p>Post: {postId}</p>
<p>Comment: {commentId}</p>
</div>
);
}
// โ
Good: Query parameters
import { useSearchParams } from 'react-router-dom';
function SearchResults() {
const [searchParams, setSearchParams] = useSearchParams();
const query = searchParams.get('q');
const page = searchParams.get('page') || 1;
const handleSearch = (newQuery) => {
setSearchParams({ q: newQuery, page: 1 });
};
return (
<div>
<input
value={query || ''}
onChange={(e) => handleSearch(e.target.value)}
placeholder="Search..."
/>
<p>Results for: {query}</p>
<p>Page: {page}</p>
</div>
);
}
Nested Routes
Route Nesting
// โ
Good: Nested routes
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="dashboard" element={<Dashboard />}>
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
);
}
// โ
Good: Layout component with Outlet
import { Outlet } from 'react-router-dom';
function Layout() {
return (
<div>
<header>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/dashboard">Dashboard</Link>
</nav>
</header>
<main>
<Outlet />
</main>
<footer>Footer</footer>
</div>
);
}
function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<nav>
<Link to="profile">Profile</Link>
<Link to="settings">Settings</Link>
</nav>
<Outlet />
</div>
);
}
function Profile() {
return <h2>Profile Page</h2>;
}
function Settings() {
return <h2>Settings Page</h2>;
}
Protected Routes
Route Guards
// โ
Good: Protected route component
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children, isAuthenticated }) {
return isAuthenticated ? children : <Navigate to="/login" />;
}
// Usage
function App() {
const [isAuthenticated, setIsAuthenticated] = React.useState(false);
return (
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<ProtectedRoute isAuthenticated={isAuthenticated}>
<Dashboard />
</ProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
);
}
// โ
Good: Role-based route protection
function RoleProtectedRoute({ children, requiredRole, userRole }) {
if (!userRole) {
return <Navigate to="/login" />;
}
if (userRole !== requiredRole) {
return <Navigate to="/unauthorized" />;
}
return children;
}
// Usage
function App() {
const [user, setUser] = React.useState(null);
return (
<BrowserRouter>
<Routes>
<Route
path="/admin"
element={
<RoleProtectedRoute requiredRole="admin" userRole={user?.role}>
<AdminPanel />
</RoleProtectedRoute>
}
/>
</Routes>
</BrowserRouter>
);
}
// โ
Good: Async route protection
function ProtectedRoute({ children }) {
const [isAuthenticated, setIsAuthenticated] = React.useState(null);
React.useEffect(() => {
checkAuth().then(setIsAuthenticated);
}, []);
if (isAuthenticated === null) {
return <p>Loading...</p>;
}
return isAuthenticated ? children : <Navigate to="/login" />;
}
Advanced Routing Patterns
Lazy Loading Routes
import { lazy, Suspense } from 'react';
// โ
Good: Lazy load route components
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<p>Loading...</p>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// โ
Good: Route-based code splitting
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route
path="admin"
element={
<Suspense fallback={<p>Loading admin...</p>}>
<AdminPanel />
</Suspense>
}
/>
</Route>
</Routes>
</BrowserRouter>
);
}
Programmatic Navigation
// โ
Good: Navigate with state
function LoginForm() {
const navigate = useNavigate();
const location = useLocation();
const handleLogin = async (credentials) => {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
const data = await response.json();
// Navigate with state
navigate('/dashboard', { state: { user: data } });
};
return (
<form onSubmit={(e) => {
e.preventDefault();
handleLogin({ email: '[email protected]', password: 'password' });
}}>
<button type="submit">Login</button>
</form>
);
}
// โ
Good: Access navigation state
import { useLocation } from 'react-router-dom';
function Dashboard() {
const location = useLocation();
const user = location.state?.user;
return <h1>Welcome, {user?.name}</h1>;
}
// โ
Good: Navigate with replace
function Redirect() {
const navigate = useNavigate();
React.useEffect(() => {
navigate('/new-path', { replace: true });
}, [navigate]);
return null;
}
// โ
Good: Navigate with history
function BackButton() {
const navigate = useNavigate();
return (
<button onClick={() => navigate(-1)}>
Go Back
</button>
);
}
Error Handling
// โ
Good: Error boundary route
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
</BrowserRouter>
);
}
function NotFound() {
return (
<div>
<h1>404 - Page Not Found</h1>
<Link to="/">Go Home</Link>
</div>
);
}
// โ
Good: Error boundary component
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('Error:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong</h1>;
}
return this.props.children;
}
}
// Usage
function App() {
return (
<ErrorBoundary>
<BrowserRouter>
<Routes>
{/* Routes */}
</Routes>
</BrowserRouter>
</ErrorBoundary>
);
}
Best Practices
-
Use relative links in nested routes:
// โ Good: Relative links <Link to="profile">Profile</Link> // โ Bad: Absolute links <Link to="/dashboard/profile">Profile</Link> -
Lazy load heavy routes:
// โ Good: Lazy load const Admin = lazy(() => import('./pages/Admin')); // โ Bad: Import everything import Admin from './pages/Admin'; -
Use useNavigate for programmatic navigation:
// โ Good: useNavigate const navigate = useNavigate(); navigate('/dashboard'); // โ Bad: window.location window.location.href = '/dashboard';
Summary
React Router is essential. Key takeaways:
- Use BrowserRouter for client-side routing
- Use Route for defining routes
- Use Link for navigation
- Use useParams for route parameters
- Use useNavigate for programmatic navigation
- Implement protected routes
- Lazy load heavy routes
- Handle 404 errors
Related Resources
Next Steps
- Learn about Form Handling
- Explore Performance Optimization
- Study Vue.js
- Practice routing patterns
- Build multi-page React applications
Comments