# Routing

When I first built the POS frontend, everything was on one page. The product list, cart, checkout form, and order history were all rendered conditionally based on state: `{view === 'products' && <ProductList />}`, `{view === 'cart' && <Cart />}`. It worked for a demo, but users couldn't bookmark pages, the back button didn't work, and the URL was always just `/`. It felt broken.

Then I added React Router. Suddenly, `/products` showed products, `/cart` showed the cart, `/orders/123` showed a specific order. Users could share links, use browser navigation, and the app felt like a real website. **Routing transforms a single-page app into a multi-page experience.**

## What Is React Router?

React Router is the standard library for routing in React. It:

* Maps URLs to components (`/products` → `<ProductList />`)
* Handles browser navigation (back/forward buttons)
* Enables programmatic navigation (`navigate('/cart')`)
* Supports URL parameters (`/products/:id`)
* Provides protected routes (require authentication)

**In the POS system, the routes are**:

* `/` - Home/Dashboard
* `/products` - Product catalog
* `/products/:id` - Product details
* `/cart` - Shopping cart
* `/checkout` - Checkout flow
* `/orders` - Order history
* `/orders/:id` - Order details
* `/login` - Login page

## Installation

```bash
npm install react-router-dom
```

For TypeScript:

```bash
npm install react-router-dom @types/react-router-dom
```

## Your First Routes

Let's set up basic routing for the POS.

```typescript
// App.tsx
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import HomePage from './pages/HomePage';
import ProductsPage from './pages/ProductsPage';
import CartPage from './pages/CartPage';
import CheckoutPage from './pages/CheckoutPage';
import OrdersPage from './pages/OrdersPage';
import NotFoundPage from './pages/NotFoundPage';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
        <Route path="/products" element={<ProductsPage />} />
        <Route path="/cart" element={<CartPage />} />
        <Route path="/checkout" element={<CheckoutPage />} />
        <Route path="/orders" element={<OrdersPage />} />
        <Route path="*" element={<NotFoundPage />} />
      </Routes>
    </BrowserRouter>
  );
}

export default App;
```

**What's happening**:

1. `<BrowserRouter>` wraps everything (provides routing context)
2. `<Routes>` contains all route definitions
3. `<Route>` maps a path to a component
4. `path="*"` catches unmatched routes (404 page)

Now visiting `/products` renders `<ProductsPage />`, `/cart` renders `<CartPage />`, etc.

## Navigation with Link

Don't use `<a>` tags for internal navigation—they cause full page reloads. Use `<Link>` instead.

```typescript
// components/Navigation.tsx
import React from 'react';
import { Link } from 'react-router-dom';

function Navigation() {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/products">Products</Link>
      <Link to="/cart">Cart</Link>
      <Link to="/orders">Orders</Link>
    </nav>
  );
}

export default Navigation;
```

**`<Link to="/products">`** renders an `<a>` tag but intercepts clicks to use client-side routing—no page reload!

### NavLink for Active Styling

```typescript
import { NavLink } from 'react-router-dom';

function Navigation() {
  return (
    <nav>
      <NavLink 
        to="/products"
        className={({ isActive }) => isActive ? 'active' : ''}
      >
        Products
      </NavLink>
      
      <NavLink 
        to="/cart"
        className={({ isActive }) => isActive ? 'active' : ''}
      >
        Cart
      </NavLink>
    </nav>
  );
}
```

**`NavLink`** adds an `active` class to the current route's link.

## Route Parameters

Dynamic routes use parameters: `/products/:id` matches `/products/123`, `/products/abc`, etc.

```typescript
// App.tsx
<Route path="/products/:id" element={<ProductDetailsPage />} />
<Route path="/orders/:orderId" element={<OrderDetailsPage />} />
```

### Accessing Parameters with useParams

```typescript
// pages/ProductDetailsPage.tsx
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';

interface Product {
  id: string;
  name: string;
  price: number;
  description: string;
  stock: number;
  imageUrl: string;
}

function ProductDetailsPage() {
  const { id } = useParams<{ id: string }>(); // Get the :id parameter
  const [product, setProduct] = useState<Product | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const fetchProduct = async () => {
      try {
        const response = await fetch(`/api/products/${id}`);
        const data = await response.json();
        setProduct(data);
      } catch (error) {
        console.error('Failed to fetch product:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchProduct();
  }, [id]); // Re-fetch if id changes
  
  if (loading) return <div>Loading...</div>;
  if (!product) return <div>Product not found</div>;
  
  return (
    <div className="product-details">
      <img src={product.imageUrl} alt={product.name} />
      <h1>{product.name}</h1>
      <p className="price">{product.price.toLocaleString()} MMK</p>
      <p>{product.description}</p>
      <p>Stock: {product.stock}</p>
      <button>Add to Cart</button>
    </div>
  );
}

export default ProductDetailsPage;
```

**URL**: `/products/abc123` → `id` = `"abc123"`

### Linking to Dynamic Routes

```typescript
// components/ProductCard.tsx
import { Link } from 'react-router-dom';

function ProductCard({ product }: { product: Product }) {
  return (
    <div className="product-card">
      <h3>{product.name}</h3>
      <p>{product.price} MMK</p>
      <Link to={`/products/${product.id}`}>View Details</Link>
    </div>
  );
}
```

## Programmatic Navigation

Sometimes you need to navigate from code (e.g., after form submission).

```typescript
// pages/CheckoutPage.tsx
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useCart } from '../hooks/useCart';

function CheckoutPage() {
  const navigate = useNavigate();
  const { cart, clearCart, total } = useCart();
  const [processing, setProcessing] = useState(false);
  
  const handleCheckout = async () => {
    try {
      setProcessing(true);
      
      const response = await fetch('/api/orders', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ items: cart, total })
      });
      
      const order = await response.json();
      
      clearCart();
      
      // Navigate to order details page
      navigate(`/orders/${order.id}`);
    } catch (error) {
      console.error('Checkout failed:', error);
      alert('Checkout failed. Please try again.');
    } finally {
      setProcessing(false);
    }
  };
  
  return (
    <div className="checkout-page">
      <h1>Checkout</h1>
      
      {cart.map(item => (
        <div key={item.productId}>{item.name} x {item.quantity}</div>
      ))}
      
      <div>Total: {total.toLocaleString()} MMK</div>
      
      <button onClick={handleCheckout} disabled={processing}>
        {processing ? 'Processing...' : 'Complete Order'}
      </button>
      
      <button onClick={() => navigate('/cart')}>Back to Cart</button>
    </div>
  );
}

export default CheckoutPage;
```

**`navigate('/orders/123')`** - Navigate to a route

**`navigate(-1)`** - Go back (like browser back button)

**`navigate(-2)`** - Go back 2 pages

## Nested Routes

For shared layouts, use nested routes.

```typescript
// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import HomePage from './pages/HomePage';
import ProductsPage from './pages/ProductsPage';
import ProductDetailsPage from './pages/ProductDetailsPage';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        {/* Routes with shared layout */}
        <Route path="/" element={<Layout />}>
          <Route index element={<HomePage />} />
          <Route path="products" element={<ProductsPage />} />
          <Route path="products/:id" element={<ProductDetailsPage />} />
          <Route path="cart" element={<CartPage />} />
          <Route path="orders" element={<OrdersPage />} />
        </Route>
        
        {/* Login page without layout */}
        <Route path="/login" element={<LoginPage />} />
      </Routes>
    </BrowserRouter>
  );
}
```

```typescript
// components/Layout.tsx
import React from 'react';
import { Outlet } from 'react-router-dom';
import Navigation from './Navigation';
import Header from './Header';
import Footer from './Footer';

function Layout() {
  return (
    <div className="layout">
      <Header />
      <Navigation />
      
      <main>
        <Outlet /> {/* Child routes render here */}
      </main>
      
      <Footer />
    </div>
  );
}

export default Layout;
```

**`<Outlet />`** is where child routes render.

**Benefits**:

* Shared header/nav across multiple pages
* Don't repeat layout code
* Clean route structure

## Protected Routes (Authentication)

Some routes should only be accessible to authenticated users.

```typescript
// components/ProtectedRoute.tsx
import React, { ReactNode } from 'react';
import { Navigate } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';

interface ProtectedRouteProps {
  children: ReactNode;
  requiredRole?: 'admin' | 'cashier' | 'manager';
}

function ProtectedRoute({ children, requiredRole }: ProtectedRouteProps) {
  const { isAuthenticated, user, loading } = useAuth();
  
  if (loading) {
    return <div>Loading...</div>;
  }
  
  if (!isAuthenticated) {
    // Redirect to login with return URL
    return <Navigate to="/login" replace />;
  }
  
  if (requiredRole && user?.role !== requiredRole) {
    // User logged in but doesn't have required role
    return <div>Access Denied</div>;
  }
  
  return <>{children}</>;
}

export default ProtectedRoute;
```

### Using Protected Routes

```typescript
// App.tsx
import ProtectedRoute from './components/ProtectedRoute';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/login" element={<LoginPage />} />
        
        {/* Public routes */}
        <Route path="/" element={<Layout />}>
          <Route index element={<HomePage />} />
          <Route path="products" element={<ProductsPage />} />
          <Route path="products/:id" element={<ProductDetailsPage />} />
        </Route>
        
        {/* Protected routes - require authentication */}
        <Route path="/" element={<Layout />}>
          <Route 
            path="cart" 
            element={
              <ProtectedRoute>
                <CartPage />
              </ProtectedRoute>
            } 
          />
          
          <Route 
            path="checkout" 
            element={
              <ProtectedRoute>
                <CheckoutPage />
              </ProtectedRoute>
            } 
          />
          
          <Route 
            path="orders" 
            element={
              <ProtectedRoute>
                <OrdersPage />
              </ProtectedRoute>
            } 
          />
          
          {/* Admin-only route */}
          <Route 
            path="admin" 
            element={
              <ProtectedRoute requiredRole="admin">
                <AdminPage />
              </ProtectedRoute>
            } 
          />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}
```

**Now**:

* Unauthenticated users → redirected to `/login`
* Regular users can access cart/checkout/orders
* Only admins can access `/admin`

## Complete POS Routing Setup

Here's the production routing structure for the POS system:

```typescript
// App.tsx
import React from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider } from './contexts/AuthContext';
import { CartProvider } from './contexts/CartProvider';
import ProtectedRoute from './components/ProtectedRoute';
import Layout from './components/Layout';

// Pages
import HomePage from './pages/HomePage';
import LoginPage from './pages/LoginPage';
import ProductsPage from './pages/ProductsPage';
import ProductDetailsPage from './pages/ProductDetailsPage';
import CartPage from './pages/CartPage';
import CheckoutPage from './pages/CheckoutPage';
import OrdersPage from './pages/OrdersPage';
import OrderDetailsPage from './pages/OrderDetailsPage';
import AdminPage from './pages/AdminPage';
import NotFoundPage from './pages/NotFoundPage';

function App() {
  return (
    <AuthProvider>
      <CartProvider>
        <BrowserRouter>
          <Routes>
            {/* Public routes */}
            <Route path="/login" element={<LoginPage />} />
            
            {/* Main app with layout */}
            <Route path="/" element={<Layout />}>
              {/* Home redirects to products */}
              <Route index element={<Navigate to="/products" replace />} />
              
              {/* Public product browsing */}
              <Route path="products">
                <Route index element={<ProductsPage />} />
                <Route path=":id" element={<ProductDetailsPage />} />
              </Route>
              
              {/* Protected routes */}
              <Route 
                path="cart" 
                element={
                  <ProtectedRoute>
                    <CartPage />
                  </ProtectedRoute>
                } 
              />
              
              <Route 
                path="checkout" 
                element={
                  <ProtectedRoute>
                    <CheckoutPage />
                  </ProtectedRoute>
                } 
              />
              
              <Route 
                path="orders" 
                element={
                  <ProtectedRoute>
                    <OrdersPage />
                  </ProtectedRoute>
                }
              >
                {/* Nested route for order details */}
                <Route path=":orderId" element={<OrderDetailsPage />} />
              </Route>
              
              {/* Admin only */}
              <Route 
                path="admin/*" 
                element={
                  <ProtectedRoute requiredRole="admin">
                    <AdminPage />
                  </ProtectedRoute>
                } 
              />
            </Route>
            
            {/* 404 */}
            <Route path="*" element={<NotFoundPage />} />
          </Routes>
        </BrowserRouter>
      </CartProvider>
    </AuthProvider>
  );
}

export default App;
```

### Enhanced Navigation Component

```typescript
// components/Navigation.tsx
import React from 'react';
import { NavLink } from 'react-router-dom';
import { useAuth } from '../hooks/useAuth';
import { useCart } from '../hooks/useCart';

function Navigation() {
  const { isAuthenticated, user, logout } = useAuth();
  const { itemCount } = useCart();
  
  return (
    <nav className="navigation">
      <div className="nav-links">
        <NavLink 
          to="/products"
          className={({ isActive }) => isActive ? 'active' : ''}
        >
          Products
        </NavLink>
        
        {isAuthenticated && (
          <>
            <NavLink to="/cart">
              Cart {itemCount > 0 && <span className="badge">{itemCount}</span>}
            </NavLink>
            
            <NavLink to="/orders">
              Orders
            </NavLink>
            
            {user?.role === 'admin' && (
              <NavLink to="/admin">
                Admin
              </NavLink>
            )}
          </>
        )}
      </div>
      
      <div className="nav-actions">
        {isAuthenticated ? (
          <>
            <span>Welcome, {user?.name}</span>
            <button onClick={logout}>Logout</button>
          </>
        ) : (
          <NavLink to="/login">Login</NavLink>
        )}
      </div>
    </nav>
  );
}

export default Navigation;
```

## Query Parameters

For filters and search, use query parameters.

```typescript
// pages/ProductsPage.tsx
import { useSearchParams } from 'react-router-dom';

function ProductsPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  
  const category = searchParams.get('category') || 'all';
  const search = searchParams.get('search') || '';
  
  const { products } = useProducts({ category, search });
  
  const handleCategoryChange = (newCategory: string) => {
    setSearchParams({ 
      category: newCategory,
      search // Preserve search
    });
  };
  
  const handleSearchChange = (newSearch: string) => {
    setSearchParams({
      category, // Preserve category
      search: newSearch
    });
  };
  
  return (
    <div>
      <input 
        value={search}
        onChange={(e) => handleSearchChange(e.target.value)}
        placeholder="Search..."
      />
      
      <select value={category} onChange={(e) => handleCategoryChange(e.target.value)}>
        <option value="all">All Categories</option>
        <option value="food">Food</option>
        <option value="drinks">Drinks</option>
      </select>
      
      {products.map(product => (
        <ProductCard key={product.id} product={product} />
      ))}
    </div>
  );
}
```

**URL examples**:

* `/products` - All products
* `/products?category=food` - Food category
* `/products?search=chicken&category=food` - Search + filter

**Benefits**:

* Shareable URLs
* Browser back/forward works
* Bookmarkable filtered views

## Redirects and Navigation Guards

### Redirect After Login

```typescript
// pages/LoginPage.tsx
import { useNavigate, useLocation } from 'react-router-dom';

function LoginPage() {
  const navigate = useNavigate();
  const location = useLocation();
  const { login } = useAuth();
  
  const handleLogin = async (email: string, password: string) => {
    await login(email, password);
    
    // Redirect to page user was trying to access, or home
    const from = location.state?.from?.pathname || '/';
    navigate(from, { replace: true });
  };
  
  return (/* login form */);
}
```

### Updated ProtectedRoute

```typescript
function ProtectedRoute({ children }: { children: ReactNode }) {
  const { isAuthenticated, loading } = useAuth();
  const location = useLocation();
  
  if (loading) return <div>Loading...</div>;
  
  if (!isAuthenticated) {
    // Save attempted location for redirect after login
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  
  return <>{children}</>;
}
```

**Flow**:

1. User tries to access `/orders` (protected)
2. Not authenticated → redirect to `/login` with state
3. User logs in
4. Redirect back to `/orders`

## Real POS Example: Complete Order Flow

```typescript
// pages/OrdersPage.tsx
import React from 'react';
import { Link } from 'react-router-dom';
import { useOrders } from '../hooks/useOrders';

function OrdersPage() {
  const { orders, loading } = useOrders();
  
  if (loading) return <div>Loading orders...</div>;
  
  if (orders.length === 0) {
    return (
      <div className="empty-state">
        <p>No orders yet</p>
        <Link to="/products">Start Shopping</Link>
      </div>
    );
  }
  
  return (
    <div className="orders-page">
      <h1>Your Orders</h1>
      
      <div className="orders-list">
        {orders.map(order => (
          <Link 
            key={order.id} 
            to={`/orders/${order.id}`}
            className="order-card"
          >
            <div className="order-header">
              <span className="order-id">#{order.id}</span>
              <span className="order-date">
                {new Date(order.createdAt).toLocaleDateString()}
              </span>
            </div>
            
            <div className="order-items">
              {order.items.length} items
            </div>
            
            <div className="order-total">
              {order.total.toLocaleString()} MMK
            </div>
            
            <div className={`order-status ${order.status}`}>
              {order.status}
            </div>
          </Link>
        ))}
      </div>
    </div>
  );
}

export default OrdersPage;
```

```typescript
// pages/OrderDetailsPage.tsx
import React from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { useOrder } from '../hooks/useOrder';

function OrderDetailsPage() {
  const { orderId } = useParams<{ orderId: string }>();
  const navigate = useNavigate();
  const { order, loading, error } = useOrder(orderId!);
  
  if (loading) return <div>Loading order...</div>;
  
  if (error || !order) {
    return (
      <div className="error-state">
        <p>Order not found</p>
        <button onClick={() => navigate('/orders')}>Back to Orders</button>
      </div>
    );
  }
  
  return (
    <div className="order-details">
      <button onClick={() => navigate('/orders')}>← Back to Orders</button>
      
      <h1>Order #{order.id}</h1>
      
      <div className="order-info">
        <p>Date: {new Date(order.createdAt).toLocaleString()}</p>
        <p>Status: <span className={order.status}>{order.status}</span></p>
      </div>
      
      <div className="order-items">
        <h2>Items</h2>
        {order.items.map(item => (
          <div key={item.productId} className="order-item">
            <span>{item.name}</span>
            <span>x{item.quantity}</span>
            <span>{(item.price * item.quantity).toLocaleString()} MMK</span>
          </div>
        ))}
      </div>
      
      <div className="order-total">
        <h3>Total: {order.total.toLocaleString()} MMK</h3>
      </div>
    </div>
  );
}

export default OrderDetailsPage;
```

## Common Mistakes to Avoid

### 1. Using `<a>` Instead of `<Link>`

```typescript
// ❌ Full page reload
<a href="/products">Products</a>

// ✅ Client-side navigation
<Link to="/products">Products</Link>
```

### 2. Forgetting BrowserRouter

```typescript
// ❌ Routes won't work
function App() {
  return (
    <Routes>
      <Route path="/" element={<HomePage />} />
    </Routes>
  );
}

// ✅ Wrap with BrowserRouter
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<HomePage />} />
      </Routes>
    </BrowserRouter>
  );
}
```

### 3. Not Handling 404s

```typescript
// ✅ Always include a catch-all route
<Route path="*" element={<NotFoundPage />} />
```

### 4. Hardcoding URLs

```typescript
// ❌ Hard to maintain
<Link to="/products/123">View Product</Link>

// ✅ Dynamic
<Link to={`/products/${product.id}`}>View Product</Link>
```

## Key Learnings

1. **React Router** maps URLs to components
2. **Use `<Link>`** for navigation, not `<a>`
3. **`useParams`** accesses route parameters
4. **`useNavigate`** for programmatic navigation
5. **`<Outlet />`** for nested routes
6. **Protected routes** require authentication
7. **Query parameters** for filters/search
8. **Always handle 404s** with catch-all route

## Next Steps

You've built a complete multi-page POS system with routing. But as the app grows, you might notice performance issues—unnecessary re-renders, slow calculations, large bundle sizes. In the final article, we'll optimize the POS for production with `useMemo`, `useCallback`, `React.memo`, code splitting, and deployment strategies.

Continue to: [Performance & Production →](https://blog.htunnthuthu.com/getting-started/programming/react-101/react-101-performance-production)
