Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

React Router v6

Cài đặt & Setup

npm install react-router-dom
// main.tsx
import { BrowserRouter } from 'react-router-dom';

createRoot(document.getElementById('root')!).render(
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

Routes Cơ bản

import { Routes, Route, Link, NavLink } from 'react-router-dom';

function App() {
  return (
    <div>
      {/* Navigation */}
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>

        {/* NavLink tự động thêm class "active" */}
        <NavLink
          to="/products"
          className={({ isActive }) => isActive ? 'nav-active' : ''}
        >
          Products
        </NavLink>
      </nav>

      {/* Routes */}
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/products" element={<Products />} />
        <Route path="/products/:productId" element={<ProductDetail />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </div>
  );
}

URL Parameters & Query Strings

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

// URL: /products/42
function ProductDetail() {
  const { productId } = useParams<{ productId: string }>();

  return <div>Product ID: {productId}</div>;
}

// URL: /products?category=electronics&page=2
function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();

  const category = searchParams.get('category') ?? 'all';
  const page = Number(searchParams.get('page') ?? '1');

  function handleCategoryChange(newCategory: string) {
    setSearchParams((prev) => {
      prev.set('category', newCategory);
      prev.set('page', '1'); // Reset page khi đổi category
      return prev;
    });
  }

  return (
    <div>
      <select
        value={category}
        onChange={(e) => handleCategoryChange(e.target.value)}
      >
        <option value="all">All</option>
        <option value="electronics">Electronics</option>
        <option value="clothing">Clothing</option>
      </select>
      <p>Category: {category}, Page: {page}</p>
    </div>
  );
}

Nested Routes

// Layout component với Outlet
import { Outlet, Link } from 'react-router-dom';

function DashboardLayout() {
  return (
    <div className="dashboard">
      <aside>
        <Link to="/dashboard">Overview</Link>
        <Link to="/dashboard/analytics">Analytics</Link>
        <Link to="/dashboard/settings">Settings</Link>
      </aside>
      <main>
        <Outlet /> {/* Render child route content */}
      </main>
    </div>
  );
}

// Route config
function App() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/dashboard" element={<DashboardLayout />}>
        <Route index element={<DashboardOverview />} />         {/* /dashboard */}
        <Route path="analytics" element={<Analytics />} />     {/* /dashboard/analytics */}
        <Route path="settings" element={<Settings />} />       {/* /dashboard/settings */}
        <Route path="users/:userId" element={<UserDetail />} />{/* /dashboard/users/:userId */}
      </Route>
    </Routes>
  );
}

Protected Routes

import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { useAuth } from './hooks/useAuth';

// Protected Route component
function ProtectedRoute({ requiredRole }: { requiredRole?: string }) {
  const { isAuthenticated, user } = useAuth();
  const location = useLocation();

  if (!isAuthenticated) {
    // Lưu location để redirect sau khi login
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  if (requiredRole && !user?.roles.includes(requiredRole)) {
    return <Navigate to="/unauthorized" replace />;
  }

  return <Outlet />;
}

// Sử dụng
function App() {
  return (
    <Routes>
      {/* Public routes */}
      <Route path="/login" element={<Login />} />
      <Route path="/register" element={<Register />} />

      {/* Protected routes */}
      <Route element={<ProtectedRoute />}>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/profile" element={<Profile />} />
      </Route>

      {/* Admin only */}
      <Route element={<ProtectedRoute requiredRole="admin" />}>
        <Route path="/admin" element={<AdminPanel />} />
      </Route>
    </Routes>
  );
}

// Login redirect sau khi đăng nhập thành công
function Login() {
  const navigate = useNavigate();
  const location = useLocation();
  const { login } = useAuth();

  const from = (location.state as { from?: Location })?.from?.pathname ?? '/dashboard';

  async function handleSubmit(credentials: Credentials) {
    await login(credentials);
    navigate(from, { replace: true }); // Redirect về trang trước
  }

  return <LoginForm onSubmit={handleSubmit} />;
}

Programmatic Navigation

import { useNavigate } from 'react-router-dom';

function ProductForm() {
  const navigate = useNavigate();

  async function handleSubmit(data: ProductData) {
    const product = await createProduct(data);
    
    // Navigate với state
    navigate(`/products/${product.id}`, {
      replace: false,   // Thêm vào history (default)
      state: { message: 'Product created successfully!' },
    });
  }

  return (
    <form onSubmit={handleSubmit}>
      {/* ... */}
      <button type="button" onClick={() => navigate(-1)}>
        Go Back
      </button>
      <button type="button" onClick={() => navigate('/products')}>
        Cancel
      </button>
    </form>
  );
}

// Đọc state từ navigation
function ProductDetail() {
  const location = useLocation();
  const message = (location.state as { message?: string })?.message;

  return (
    <div>
      {message && <Alert>{message}</Alert>}
      {/* ... */}
    </div>
  );
}

Lazy Loading Routes

import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

// Lazy load components
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Analytics = lazy(() => import('./pages/Analytics'));
const AdminPanel = lazy(() => import('./pages/AdminPanel'));

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/analytics" element={<Analytics />} />
        <Route path="/admin" element={<AdminPanel />} />
      </Routes>
    </Suspense>
  );
}

Link / NavLinkuseNavigate
Dùng khiNavigation trong JSXNavigation trong event handlers
Accessibility✅ Semantic <a> tag❌ Cần thêm manually
Ví dụMenu items, buttonsForm submit, callback