Skip to content

React Routing

Overview

React Router is the most popular routing library in React applications, enabling us to create client-side routing in Single Page Applications (SPA). This chapter will learn how to use React Router to create multi-page applications.

🚀 Basic Routing Configuration

Installation and Basic Setup

bash
npm install react-router-dom
jsx
import { BrowserRouter, Routes, Route, Link, useNavigate } from 'react-router-dom';

// Page components
function Home() {
  return (
    <div style={{ padding: '20px' }}>
      <h1>Home</h1>
      <p>Welcome to our website!</p>
    </div>
  );
}

function About() {
  return (
    <div style={{ padding: '20px' }}>
      <h1>About Us</h1>
      <p>This is the about page content.</p>
    </div>
  );
}

function Contact() {
  const navigate = useNavigate();
  
  return (
    <div style={{ padding: '20px' }}>
      <h1>Contact Us</h1>
      <p>Contact information: contact@example.com</p>
      <button onClick={() => navigate('/')}>
        Back to Home
      </button>
    </div>
  );
}

function NotFound() {
  return (
    <div style={{ padding: '20px', textAlign: 'center' }}>
      <h1>404 - Page Not Found</h1>
      <p>Sorry, the page you are looking for does not exist.</p>
      <Link to="/">Back to Home</Link>
    </div>
  );
}

// Navigation component
function Navigation() {
  const navStyle = {
    backgroundColor: '#343a40',
    padding: '1rem'
  };
  
  const linkStyle = {
    color: 'white',
    textDecoration: 'none',
    marginRight: '20px',
    padding: '8px 16px',
    borderRadius: '4px',
    transition: 'background-color 0.3s'
  };
  
  return (
    <nav style={navStyle}>
      <Link to="/" style={linkStyle}>Home</Link>
      <Link to="/about" style={linkStyle}>About</Link>
      <Link to="/contact" style={linkStyle}>Contact</Link>
    </nav>
  );
}

// Main application
function RouterBasicExample() {
  return (
    <BrowserRouter>
      <div>
        <Navigation />
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/contact" element={<Contact />} />
          <Route path="*" element={<NotFound />} />
        </Routes>
      </div>
    </BrowserRouter>
  );
}

🔗 Dynamic Routing and Parameters

Route Parameters

jsx
import { useParams, useSearchParams } from 'react-router-dom';

// User detail page
function UserDetail() {
  const { userId } = useParams();
  const [searchParams] = useSearchParams();
  const tab = searchParams.get('tab') || 'profile';
  
  // Simulate user data
  const users = {
    '1': { name: 'Zhang San', email: 'zhangsan@example.com', role: 'admin' },
    '2': { name: 'Li Si', email: 'lisi@example.com', role: 'user' },
    '3': { name: 'Wang Wu', email: 'wangwu@example.com', role: 'user' }
  };
  
  const user = users[userId];
  
  if (!user) {
    return <div>User not found</div>;
  }
  
  const renderTabContent = () => {
    switch (tab) {
      case 'profile':
        return (
          <div>
            <h3>Profile</h3>
            <p>Name: {user.name}</p>
            <p>Email: {user.email}</p>
            <p>Role: {user.role}</p>
          </div>
        );
      case 'settings':
        return (
          <div>
            <h3>Settings</h3>
            <p>This is the user settings page</p>
          </div>
        );
      case 'orders':
        return (
          <div>
            <h3>Order History</h3>
            <p>This displays user's order history</p>
          </div>
        );
      default:
        return <div>Unknown tab</div>;
    }
  };
  
  return (
    <div style={{ padding: '20px' }}>
      <h1>User Detail - {user.name}</h1>
      
      <div style={{ marginBottom: '20px' }}>
        <Link 
          to={`/users/${userId}?tab=profile`}
          style={{ 
            marginRight: '10px', 
            padding: '8px 16px', 
            backgroundColor: tab === 'profile' ? '#007bff' : '#f8f9fa',
            color: tab === 'profile' ? 'white' : '#333',
            textDecoration: 'none',
            borderRadius: '4px'
          }}
        >
          Profile
        </Link>
        <Link 
          to={`/users/${userId}?tab=settings`}
          style={{ 
            marginRight: '10px', 
            padding: '8px 16px', 
            backgroundColor: tab === 'settings' ? '#007bff' : '#f8f9fa',
            color: tab === 'settings' ? 'white' : '#333',
            textDecoration: 'none',
            borderRadius: '4px'
          }}
        >
          Settings
        </Link>
        <Link 
          to={`/users/${userId}?tab=orders`}
          style={{ 
            padding: '8px 16px', 
            backgroundColor: tab === 'orders' ? '#007bff' : '#f8f9fa',
            color: tab === 'orders' ? 'white' : '#333',
            textDecoration: 'none',
            borderRadius: '4px'
          }}
        >
          Orders
        </Link>
      </div>
      
      <div style={{
        border: '1px solid #ddd',
        borderRadius: '8px',
        padding: '20px',
        backgroundColor: '#f8f9fa'
      }}>
        {renderTabContent()}
      </div>
    </div>
  );
}

// User list page
function UserList() {
  const users = [
    { id: 1, name: 'Zhang San', role: 'admin' },
    { id: 2, name: 'Li Si', role: 'user' },
    { id: 3, name: 'Wang Wu', role: 'user' }
  ];
  
  return (
    <div style={{ padding: '20px' }}>
      <h1>User List</h1>
      <div style={{ display: 'grid', gap: '10px' }}>
        {users.map(user => (
          <div key={user.id} style={{
            padding: '15px',
            border: '1px solid #ddd',
            borderRadius: '8px',
            display: 'flex',
            justifyContent: 'space-between',
            alignItems: 'center'
          }}>
            <div>
              <h3 style={{ margin: '0 0 5px 0' }}>{user.name}</h3>
              <span style={{ color: '#666', fontSize: '14px' }}>{user.role}</span>
            </div>
            <Link 
              to={`/users/${user.id}`}
              style={{
                padding: '8px 16px',
                backgroundColor: '#007bff',
                color: 'white',
                textDecoration: 'none',
                borderRadius: '4px'
              }}
            >
              View Details
            </Link>
          </div>
        ))}
      </div>
    </div>
  );
}

🛡️ Route Guards and Permissions

Authenticated Routes

jsx
// Auth context
const AuthContext = React.createContext();

function AuthProvider({ children }) {
  const [user, setUser] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  
  React.useEffect(() => {
    // Simulate checking login status
    setTimeout(() => {
      const savedUser = localStorage.getItem('user');
      if (savedUser) {
        setUser(JSON.parse(savedUser));
      }
      setLoading(false);
    }, 1000);
  }, []);
  
  const login = (username, password) => {
    // Simulate login
    if (username === 'admin' && password === 'password') {
      const userData = { username: 'admin', role: 'admin' };
      setUser(userData);
      localStorage.setItem('user', JSON.stringify(userData));
      return true;
    }
    return false;
  };
  
  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };
  
  return (
    <AuthContext.Provider value={{ user, login, logout, loading }}>
      {children}
    </AuthContext.Provider>
  );
}

function useAuth() {
  return React.useContext(AuthContext);
}

// Protected route component
function ProtectedRoute({ children, requiredRole }) {
  const { user, loading } = useAuth();
  const navigate = useNavigate();
  
  React.useEffect(() => {
    if (!loading && !user) {
      navigate('/login');
    }
  }, [user, loading, navigate]);
  
  if (loading) {
    return <div style={{ padding: '20px', textAlign: 'center' }}>Loading...</div>;
  }
  
  if (!user) {
    return null;
  }
  
  if (requiredRole && user.role !== requiredRole) {
    return (
      <div style={{ padding: '20px', textAlign: 'center' }}>
        <h2>Insufficient Permissions</h2>
        <p>You do not have permission to access this page.</p>
      </div>
    );
  }
  
  return children;
}

// Login page
function Login() {
  const [username, setUsername] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [error, setError] = React.useState('');
  const { login } = useAuth();
  const navigate = useNavigate();
  
  const handleSubmit = (e) => {
    e.preventDefault();
    if (login(username, password)) {
      navigate('/dashboard');
    } else {
      setError('Invalid username or password');
    }
  };
  
  return (
    <div style={{ 
      padding: '20px', 
      maxWidth: '400px', 
      margin: '50px auto',
      border: '1px solid #ddd',
      borderRadius: '8px'
    }}>
      <h2>Login</h2>
      <form onSubmit={handleSubmit}>
        <div style={{ marginBottom: '15px' }}>
          <input
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
            placeholder="Username (admin)"
            style={{ width: '100%', padding: '8px' }}
          />
        </div>
        <div style={{ marginBottom: '15px' }}>
          <input
            type="password"
            value={password}
            onChange={(e) => setPassword(e.target.value)}
            placeholder="Password (password)"
            style={{ width: '100%', padding: '8px' }}
          />
        </div>
        {error && <div style={{ color: 'red', marginBottom: '15px' }}>{error}</div>}
        <button type="submit" style={{ width: '100%', padding: '10px' }}>
          Login
        </button>
      </form>
    </div>
  );
}

// Dashboard page
function Dashboard() {
  const { user, logout } = useAuth();
  
  return (
    <div style={{ padding: '20px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <h1>Dashboard</h1>
        <div>
          <span>Welcome, {user.username}!</span>
          <button onClick={logout} style={{ marginLeft: '10px' }}>
            Logout
          </button>
        </div>
      </div>
      <p>This is the protected dashboard page.</p>
    </div>
  );
}

// Admin page
function AdminPanel() {
  return (
    <div style={{ padding: '20px' }}>
      <h1>Admin Panel</h1>
      <p>Only administrators can access this page.</p>
    </div>
  );
}

🔄 Routing State Management

Complete Routing Application

jsx
function CompleteRoutingApp() {
  return (
    <AuthProvider>
      <BrowserRouter>
        <div>
          <Routes>
            <Route path="/login" element={<Login />} />
            <Route path="/" element={<Home />} />
            <Route path="/about" element={<About />} />
            <Route path="/users" element={<UserList />} />
            <Route path="/users/:userId" element={<UserDetail />} />
            <Route 
              path="/dashboard" 
              element={
                <ProtectedRoute>
                  <Dashboard />
                </ProtectedRoute>
              } 
            />
            <Route 
              path="/admin" 
              element={
                <ProtectedRoute requiredRole="admin">
                  <AdminPanel />
                </ProtectedRoute>
              } 
            />
            <Route path="*" element={<NotFound />} />
          </Routes>
        </div>
      </BrowserRouter>
    </AuthProvider>
  );
}

📝 Chapter Summary

Through this chapter, you should have mastered:

Routing Core Concepts

  • ✅ BrowserRouter and routing configuration
  • ✅ Dynamic routing and parameter retrieval
  • ✅ Route guards and permission control
  • ✅ Programmatic navigation

Best Practices

  1. Routing Organization: Reasonably plan routing structure
  2. Permission Control: Implement route-level permission management
  3. User Experience: Provide loading states and error pages
  4. Code Splitting: Use lazy loading for performance optimization

Continue Learning: Next Chapter - React State Management Redux

Content is for learning and research only.