# Lab 14: React #4 - Router & Deployment

## 🎯 วัตถุประสงค์

หลังจากทำ Lab นี้ เมื่อจบแล้ว นิสิตสามารถ

- ติดตั้งและใช้งาน React Router v6
- สร้างแอปหลายหน้า (Multi-page App)
- ใช้ Link, useNavigate, useParams
- ส่งข้อมูลข้ามหน้าด้วย useContext
- Deploy React App ขึ้น Vercel หรือ Netlify

---

## 📋 ส่วนที่ 1: React Router Setup

### ขั้นตอน 1.1: ติดตั้ง React Router

```bash
# ใน terminal ของโปรเจค
npm install react-router-dom
```

### ขั้นตอน 1.2: โครงสร้างโปรเจค

```
src/
├── pages/
│   ├── Home.jsx       ← หน้าหลัก
│   ├── About.jsx      ← หน้าเกี่ยวกับ
│   └── NotFound.jsx   ← หน้า 404
├── components/
│   └── Navbar.jsx     ← เมนูนำทาง
└── App.jsx            ← กำหนด Routes
```

### ขั้นตอน 1.3: กำหนด Routes ใน App.jsx

```javascript
// src/App.jsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Navbar from "./components/Navbar";
import Home from "./pages/Home";
import About from "./pages/About";
import NotFound from "./pages/NotFound";

export default function App() {
  return (
    <BrowserRouter>
      <Navbar />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}
```

### ขั้นตอน 1.4: สร้าง Navbar

```javascript
// src/components/Navbar.jsx
import { Link, NavLink } from "react-router-dom";

export default function Navbar() {
  return (
    <nav
      style={{
        backgroundColor: "#0066cc",
        padding: "15px 20px",
        display: "flex",
        gap: "20px",
        alignItems: "center",
      }}
    >
      <Link
        to="/"
        style={{
          color: "white",
          fontWeight: "bold",
          fontSize: "20px",
          textDecoration: "none",
        }}
      >
        🏠 MyApp
      </Link>

      <div style={{ display: "flex", gap: "15px", marginLeft: "auto" }}>
        {/* NavLink เพิ่ม active class อัตโนมัติ */}
        <NavLink
          to="/"
          end
          style={({ isActive }) => ({
            color: "white",
            textDecoration: "none",
            fontWeight: isActive ? "bold" : "normal",
            borderBottom: isActive ? "2px solid white" : "none",
          })}
        >
          Home
        </NavLink>

        <NavLink
          to="/about"
          style={({ isActive }) => ({
            color: "white",
            textDecoration: "none",
            fontWeight: isActive ? "bold" : "normal",
            borderBottom: isActive ? "2px solid white" : "none",
          })}
        >
          About
        </NavLink>
      </div>
    </nav>
  );
}
```

### ขั้นตอน 1.5: สร้างหน้าพื้นฐาน

```javascript
// src/pages/Home.jsx
export default function Home() {
  return (
    <div style={{ padding: "40px" }}>
      <h1>🏠 หน้าหลัก</h1>
      <p>ยินดีต้อนรับสู่แอปของเรา!</p>
    </div>
  );
}
```

```javascript
// src/pages/About.jsx
export default function About() {
  return (
    <div style={{ padding: "40px" }}>
      <h1>ℹ️ เกี่ยวกับเรา</h1>
      <p>นี่คือแอปพลิเคชัน React ที่สร้างใน Lab 14</p>
    </div>
  );
}
```

```javascript
// src/pages/NotFound.jsx
import { Link } from "react-router-dom";

export default function NotFound() {
  return (
    <div style={{ padding: "40px", textAlign: "center" }}>
      <h1>404 - ไม่พบหน้านี้</h1>
      <p>หน้าที่คุณต้องการไม่มีอยู่</p>
      <Link to="/">กลับหน้าหลัก</Link>
    </div>
  );
}
```

**ทดสอบ:** คลิก link ใน Navbar และดูว่า URL เปลี่ยนแต่หน้าไม่ reload

<!-- ใส่รูป lab14-1.pnd -->

![หน้าหลัก](lab14-1.png)

---

## 📋 ส่วนที่ 2: Dynamic Routes & useParams

### ขั้นตอน 2.1: สร้างหน้า Products

```javascript
// src/pages/Products.jsx
import { Link } from "react-router-dom";

const products = [
  { id: 1, name: "Laptop", price: 35000, category: "Electronics" },
  { id: 2, name: "Phone", price: 15000, category: "Electronics" },
  { id: 3, name: "Shirt", price: 500, category: "Clothing" },
  { id: 4, name: "Pants", price: 800, category: "Clothing" },
];

export default function Products() {
  return (
    <div style={{ padding: "20px" }}>
      <h1>🛍️ สินค้าทั้งหมด</h1>
      <div style={{ display: "flex", flexWrap: "wrap", gap: "15px" }}>
        {products.map((product) => (
          <div
            key={product.id}
            style={{
              border: "1px solid #ddd",
              borderRadius: "8px",
              padding: "20px",
              width: "200px",
            }}
          >
            <h3>{product.name}</h3>
            <p>฿{product.price.toLocaleString()}</p>
            {/* Link ไปหน้ารายละเอียด */}
            <Link to={`/products/${product.id}`}>ดูรายละเอียด →</Link>
          </div>
        ))}
      </div>
    </div>
  );
}
```

### ขั้นตอน 2.2: สร้างหน้า Product Detail ด้วย useParams

```javascript
// src/pages/ProductDetail.jsx
import { useParams, Link, useNavigate } from "react-router-dom";

const products = [
  {
    id: 1,
    name: "Laptop",
    price: 35000,
    category: "Electronics",
    description: "โน้ตบุ๊กประสิทธิภาพสูง",
  },
  {
    id: 2,
    name: "Phone",
    price: 15000,
    category: "Electronics",
    description: "สมาร์ทโฟนรุ่นล่าสุด",
  },
  {
    id: 3,
    name: "Shirt",
    price: 500,
    category: "Clothing",
    description: "เสื้อผ้าคุณภาพดี",
  },
  {
    id: 4,
    name: "Pants",
    price: 800,
    category: "Clothing",
    description: "กางเกงใส่สบาย",
  },
];

export default function ProductDetail() {
  const { id } = useParams(); // ดึง :id จาก URL
  const navigate = useNavigate();

  const product = products.find((p) => p.id === Number(id));

  if (!product) {
    return (
      <div style={{ padding: "40px" }}>
        <p>ไม่พบสินค้า</p>
        <Link to="/products">กลับ</Link>
      </div>
    );
  }

  return (
    <div style={{ padding: "40px", maxWidth: "600px" }}>
      <button onClick={() => navigate(-1)}>← ย้อนกลับ</button>

      <h1>{product.name}</h1>
      <p style={{ fontSize: "28px", color: "#e74c3c" }}>
        ฿{product.price.toLocaleString()}
      </p>
      <p>หมวดหมู่: {product.category}</p>
      <p>{product.description}</p>
      <button
        style={{
          padding: "12px 30px",
          backgroundColor: "#0066cc",
          color: "white",
          border: "none",
          borderRadius: "6px",
          cursor: "pointer",
          fontSize: "16px",
        }}
      >
        🛒 เพิ่มลงตะกร้า
      </button>
    </div>
  );
}
```

### ขั้นตอน 2.3: เพิ่ม Routes ใหม่ใน App.jsx

```javascript
// src/App.jsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Navbar from "./components/Navbar";
import Home from "./pages/Home";
import About from "./pages/About";
import Products from "./pages/Products";
import ProductDetail from "./pages/ProductDetail";
import NotFound from "./pages/NotFound";

export default function App() {
  return (
    <BrowserRouter>
      <Navbar />
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/products" element={<Products />} />
        <Route path="/products/:id" element={<ProductDetail />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}
```

เพิ่ม link ใน Navbar:

```javascript
// เพิ่มใน Navbar.jsx
<NavLink to="/products" style={...}>Products</NavLink>
```

![หน้าหลัก](lab14-2.png)
![หน้าหลัก](lab14-21.png)

---

## 📋 ส่วนที่ 3: useContext - แชร์ข้อมูลข้ามหน้า

### ขั้นตอน 3.1: สร้าง CartContext

```javascript
// src/contexts/CartContext.jsx
import { createContext, useContext, useState } from "react";

const CartContext = createContext(null);

export function CartProvider({ children }) {
  const [cartItems, setCartItems] = useState([]);

  function addToCart(product) {
    setCartItems((prev) => {
      const exists = prev.find((item) => item.id === product.id);
      if (exists) return prev; // ไม่เพิ่มซ้ำ
      return [...prev, product];
    });
  }

  function removeFromCart(id) {
    setCartItems((prev) => prev.filter((item) => item.id !== id));
  }

  return (
    <CartContext.Provider value={{ cartItems, addToCart, removeFromCart }}>
      {children}
    </CartContext.Provider>
  );
}

export function useCart() {
  return useContext(CartContext);
}
```

### ขั้นตอน 3.2: ครอบ App ด้วย CartProvider

```javascript
// src/main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { CartProvider } from "./contexts/CartContext";
import App from "./App";
import "./index.css";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <CartProvider>
      <App />
    </CartProvider>
  </StrictMode>,
);
```

### ขั้นตอน 3.3: แสดง Cart ใน Navbar

```javascript
// เพิ่มใน Navbar.jsx
import { useCart } from '../contexts/CartContext';

export default function Navbar() {
  const { cartItems } = useCart();

  return (
    <nav style={{ ... }}>
      {/* ... links เดิม ... */}

      <NavLink to="/cart" style={...}>
        🛒 Cart {cartItems.length > 0 && (
          <span style={{
            background: 'red',
            color: 'white',
            borderRadius: '50%',
            padding: '2px 7px',
            fontSize: '12px',
          }}>
            {cartItems.length}
          </span>
        )}
      </NavLink>
    </nav>
  );
}
```

### ขั้นตอน 3.4: ใช้ addToCart ใน ProductDetail

```javascript
// แก้ไข src/pages/ProductDetail.jsx
import { useCart } from "../contexts/CartContext";

export default function ProductDetail() {
  const { id } = useParams();
  const navigate = useNavigate();
  const { addToCart, cartItems } = useCart();

  const product = products.find((p) => p.id === Number(id));
  const inCart = cartItems.some((item) => item.id === product?.id);

  // ...

  return (
    <div style={{ padding: "40px" }}>
      {/* ... รายละเอียดสินค้า ... */}

      <button
        onClick={() => addToCart(product)}
        disabled={inCart}
        style={{
          padding: "12px 30px",
          backgroundColor: inCart ? "#ccc" : "#0066cc",
          color: "white",
          border: "none",
          borderRadius: "6px",
          cursor: inCart ? "not-allowed" : "pointer",
        }}
      >
        {inCart ? "✅ อยู่ในตะกร้าแล้ว" : "🛒 เพิ่มลงตะกร้า"}
      </button>
    </div>
  );
}
```

### ขั้นตอน 3.5: สร้างหน้า Cart

```javascript
// src/pages/Cart.jsx
import { Link } from "react-router-dom";
import { useCart } from "../contexts/CartContext";

export default function Cart() {
  const { cartItems, removeFromCart } = useCart();

  const total = cartItems.reduce((sum, item) => sum + item.price, 0);

  if (cartItems.length === 0) {
    return (
      <div style={{ padding: "40px", textAlign: "center" }}>
        <h1>🛒 ตะกร้าสินค้า</h1>
        <p>ยังไม่มีสินค้าในตะกร้า</p>
        <Link to="/products">เลือกซื้อสินค้า</Link>
      </div>
    );
  }

  return (
    <div style={{ padding: "40px", maxWidth: "600px" }}>
      <h1>🛒 ตะกร้าสินค้า ({cartItems.length} รายการ)</h1>

      {cartItems.map((item) => (
        <div
          key={item.id}
          style={{
            display: "flex",
            justifyContent: "space-between",
            alignItems: "center",
            padding: "15px",
            borderBottom: "1px solid #eee",
          }}
        >
          <span>{item.name}</span>
          <span>฿{item.price.toLocaleString()}</span>
          <button onClick={() => removeFromCart(item.id)}>ลบ ✕</button>
        </div>
      ))}

      <div style={{ marginTop: "20px", textAlign: "right", fontSize: "20px" }}>
        <strong>รวม: ฿{total.toLocaleString()}</strong>
      </div>

      <button
        style={{
          marginTop: "20px",
          padding: "15px 40px",
          backgroundColor: "#27ae60",
          color: "white",
          border: "none",
          borderRadius: "6px",
          cursor: "pointer",
          fontSize: "18px",
          width: "100%",
        }}
      >
        สั่งซื้อ
      </button>
    </div>
  );
}
```

## ![หน้าหลัก](lab14-3.png)

## 📋 ส่วนที่ 4: Deployment

### ขั้นตอน 4.1: Build โปรเจค

```bash
# Build สำหรับ production
npm run build

# โฟลเดอร์ dist/ จะถูกสร้างขึ้น
```

ตรวจสอบผลลัพธ์:

```
dist/
├── index.html
└── assets/
    ├── index-xxx.js    ← JavaScript ทั้งหมด (minified)
    └── index-xxx.css   ← CSS ทั้งหมด (minified)
```

### ขั้นตอน 4.2: Deploy ด้วย Vercel (แนะนำ)

**วิธีที่ 1: ผ่าน GitHub (ง่ายที่สุด)**

1. Push code ขึ้น GitHub ก่อน:

```bash
git add .
git commit -m "Lab14: React Router & Deployment"
git push
```

2. ไปที่ [vercel.com](https://vercel.com) → Sign in with GitHub

3. กด **Add New Project** → เลือก repository ของคุณ

4. กด **Deploy** (Vercel ตรวจจับ Vite อัตโนมัติ)

5. รอสักครู่ → ได้ URL เช่น `https://your-app.vercel.app`

**วิธีที่ 2: Vercel CLI**

```bash
# ติดตั้ง Vercel CLI
npm install -g vercel

# Deploy
vercel

# ตอบคำถาม:
# Set up and deploy: Y
# Which scope: เลือก account ของคุณ
# Link to existing project: N
# Project name: [enter ชื่อที่ต้องการ]
# Directory: ./
# Override settings: N
```

### ขั้นตอน 4.3: แก้ปัญหา Routing บน Vercel

React Router ใช้ client-side routing เมื่อ refresh หน้าอาจ 404 แก้ไขโดยสร้างไฟล์:

```
# สร้างไฟล์ vercel.json ที่ root ของโปรเจค
```

```json
{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ]
}
```

### ขั้นตอน 4.4: Deploy ด้วย Netlify (ทางเลือก)

1. ไปที่ [netlify.com](https://netlify.com) → Sign in with GitHub

2. กด **Add new site** → **Import an existing project**

3. เลือก repository → ตั้งค่า:
   - Build command: `npm run build`
   - Publish directory: `dist`

4. กด **Deploy site**

**แก้ปัญหา Routing บน Netlify:** สร้างไฟล์ `public/_redirects`:

```
/*    /index.html   200
```

---

## 📋 ส่วนที่ 5: Challenge

### Challenge: สร้าง Multi-page Portfolio

สร้างแอป Portfolio ส่วนตัวที่มีหน้าต่อไปนี้:

| หน้า           | URL             | เนื้อหา                               |
| -------------- | --------------- | ------------------------------------- |
| Home           | `/`             | ชื่อ, คำแนะนำตัว, ปุ่มไปหน้า Projects |
| About          | `/about`        | ประวัติ, ทักษะ, การศึกษา              |
| Projects       | `/projects`     | รายการโปรเจค (อย่างน้อย 3 ชิ้น)       |
| Project Detail | `/projects/:id` | รายละเอียดของโปรเจคแต่ละชิ้น          |
| Contact        | `/contact`      | Form ส่งข้อความ (ไม่ต้องส่งจริง)      |

**Features ที่ต้องมี:**

1. Navbar ที่ highlight หน้าปัจจุบัน (NavLink active)
2. ปุ่มย้อนกลับใน Project Detail (useNavigate)
3. Page ที่ 404 ด้วย link กลับหน้าหลัก
4. Deploy ขึ้น Vercel หรือ Netlify
5. ลิงก์ live URL ใน README.md

---

## ✅ Checklist

- [ ] ติดตั้ง React Router v6
- [ ] สร้าง Routes ได้ถูกต้อง (BrowserRouter, Routes, Route)
- [ ] ใช้ Link และ NavLink ได้
- [ ] สร้าง Dynamic Route ด้วย useParams
- [ ] ใช้ useNavigate เพื่อ navigate แบบ programmatic
- [ ] ใช้ useContext แชร์ข้อมูลข้ามหน้า
- [ ] Build โปรเจคด้วย npm run build
- [ ] Deploy ขึ้น Vercel/Netlify สำเร็จ

---

## 📊 Router Hooks สรุป

| Hook              | ใช้เพื่อ                  | ตัวอย่าง                                       |
| ----------------- | ------------------------- | ---------------------------------------------- |
| `useParams`       | ดึง URL parameter         | `/products/:id` → `const { id } = useParams()` |
| `useNavigate`     | navigate แบบ programmatic | `navigate('/home')`, `navigate(-1)`            |
| `useLocation`     | ดึงข้อมูล URL ปัจจุบัน    | `const { pathname } = useLocation()`           |
| `useSearchParams` | ดึง query string          | `/search?q=react` → `searchParams.get('q')`    |

---

## 🧪 การทดสอบ (Testing)

### ทดสอบ 1: Routing

- คลิกทุก link ใน Navbar ดูว่า URL เปลี่ยนถูกต้อง
- Refresh หน้าในแต่ละ route ต้องไม่ 404 (เฉพาะ production)
- พิมพ์ URL ที่ไม่มีอยู่ต้องเห็นหน้า 404

### ทดสอบ 2: Dynamic Route

- คลิกสินค้าแต่ละชิ้น URL ต้องเปลี่ยน `/products/1`, `/products/2`
- กดปุ่มย้อนกลับต้องกลับหน้าเดิม
- พิมพ์ `/products/99` ต้องแสดง "ไม่พบสินค้า"

### ทดสอบ 3: Context

- เพิ่มสินค้าในหน้า Detail → ตัวเลขใน Navbar ต้องเพิ่ม
- ไปหน้า Cart ต้องเห็นสินค้าที่เพิ่มไว้

### ทดสอบ 4: Deployment

- เปิด URL ที่ deploy แล้วในเบราว์เซอร์อื่น
- ทดสอบ routing ทุกหน้า

---

## 📚 ทรัพยากรเพิ่มเติม

- [React Router v6 Docs](https://reactrouter.com/en/main)
- [Vercel Documentation](https://vercel.com/docs)
- [Netlify Documentation](https://docs.netlify.com/)

---

## 📝 คำถามท้ายปฏิบัติการ - วัดความเข้าใจ

### ส่วนที่ 1: React Router Basics

**คำถาม 1.1:** `<Link>` และ `<NavLink>` ต่างกันอย่างไร? และแต่ละอันเหมาะกับ use case ไหน?

**คำตอบ:**

```
Link:
- ใช้สำหรับ navigation ทั่วไป
- ไม่มี active state

NavLink:
- เหมาะสำหรับเมนู navigation
- มี active state อัตโนมัติเมื่อ URL ตรงกับ to prop
- รับ callback function ใน style/className เพื่อ conditional styling
```

---

**คำถาม 1.2:** Route แบบ Dynamic ทำงานอย่างไร? และ `useParams` ดึงข้อมูลอะไร?

---

**คำถาม 1.3:** `useNavigate` ใช้เพื่ออะไร? และ `navigate(-1)` หมายความว่าอะไร?

---

### ส่วนที่ 2: useContext

**คำถาม 2.1:** เหตุใดจึงต้องใช้ Context แทนการส่ง Props ในแอปที่มีหลายหน้า? ยกตัวอย่างข้อมูลที่เหมาะกับ Context

---

**คำถาม 2.2:** ถ้า Component ใช้ `useContext` แต่ไม่ได้อยู่ใน Provider จะเกิดอะไรขึ้น? วิธีป้องกัน?

---

### ส่วนที่ 3: Deployment

**คำถาม 3.1:** ทำไม React Router ถึงต้องมีการ configure เพิ่มเติมบน Vercel/Netlify? อธิบายหลักการ

**คำตอบ:**

```
React Router ใช้ client-side routing:
- URL เปลี่ยนโดยไม่มีการ request ไปที่ server
- เมื่อ refresh หน้า → browser ส่ง request ไปที่ server
- Server หา /products/1 ไม่เจอ → 404

วิธีแก้:
- ให้ server redirect ทุก request กลับมาที่ index.html
- React Router จะจัดการ routing เอง (vercel.json, _redirects)
```

---

**คำถาม 3.2:** `npm run build` ทำอะไร? ทำไมถึงต้อง build ก่อน deploy?

---
