Skip to main content
โšก Calmops

React Router: Navigation and Routing

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

  1. Use relative links in nested routes:

    // โœ… Good: Relative links
    <Link to="profile">Profile</Link>
    
    // โŒ Bad: Absolute links
    <Link to="/dashboard/profile">Profile</Link>
    
  2. Lazy load heavy routes:

    // โœ… Good: Lazy load
    const Admin = lazy(() => import('./pages/Admin'));
    
    // โŒ Bad: Import everything
    import Admin from './pages/Admin';
    
  3. 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

Next Steps

Comments