# สัปดาห์ที่ 13 - React #3: useEffect & API

## สารบัญ

1. [useEffect Hook - จัดการ Side Effects](#useeffect-hook---จัดการ-side-effects)
   - [useEffect คืออะไร](#useeffect-คืออะไร)
   - [ทำไมถึงเรียกว่า Side Effect](#ทำไมถึงเรียกว่า-side-effect)
   - [ตัวอย่างง่าย: Timer Component](#ตัวอย่างง่าย-timer-component)
   - [โครงสร้างพื้นฐาน](#โครงสร้างพื้นฐาน)
   - [Dependency Array](#dependency-array---ตรวจสอบการเปลี่ยนแปลง)

2. [Fetching Data - เรียก API](#fetching-data---เรียก-api)
   - [Fetch API คืออะไร](#fetch-api-คืออะไร)
   - [async/await คืออะไร](#asyncawait-คืออะไร)
   - [ตัวอย่าง 1: ดึงข้อมูลจาก JSONPlaceholder](#ตัวอย่าง-1-ดึงข้อมูลจาก-jsonplaceholder)
   - [ตัวอย่าง 2: ดึงรายการข้อมูล](#ตัวอย่าง-2-ดึงรายการข้อมูล)
   - [ตัวอย่าง 3: ดึงข้อมูลตามเงื่อนไข](#ตัวอย่าง-3-ดึงข้อมูลตามเงื่อนไข)

3. [Loading + Error States](#loading--error-states)
   - [Pattern: Loading, Success, Error](#pattern-loading-success-error)
   - [ตัวอย่าง: การแสดง State ต่างๆ](#ตัวอย่าง-การแสดง-state-ต่างๆ)

4. [Cleanup Function](#cleanup-function)

5. [ตัวอย่างจริง: Fetch Data + Display in React](#ตัวอย่างจริง-fetch-data--display-in-react)

6. [Best Practices: AbortController](#best-practices-abortcontroller)

7. [Advanced Pattern: React Query / SWR](#advanced-pattern-react-query--swr)

8. [Common Conceptual Mistakes](#common-conceptual-mistakes)

9. [Quick Reference: Mistakes สรุป](#quick-reference-mistakes-สรุป)

10. [สรุปสัปดาห์ที่ 13](#สรุปสัปดาห์ที่-13)

11. [REST API ที่สามารถใช้ทดสอบ](#rest-api-ที่สามารถใช้ทดสอบ)

12. [สัปดาห์หน้า](#สัปดาห์หน้า)

---

## useEffect Hook - จัดการ Side Effects

บทนี้จะศึกษาวิธีใช้ useEffect Hook สำหรับจัดการ side effects เช่น การเรียก API, ตั้งค่า Timer, การ subscribe event, การแก้ไข DOM โดยตรงหรือจัดการ Event Listener เมื่อ Component mount

### useEffect คืออะไร

**useEffect** คือ Hook ที่ใช้จัดการ side effects - ผลข้างเคียง หรือ**สิ่งที่เกิดขึ้นหลังจากการ render** เช่น:

- เรียก API
- ตั้งค่า Timer
- บันทึกข้อมูลลง localStorage
- เพิ่ม/ลบ Event Listener

### ทำไมถึงเรียกว่า Side Effect?

เพราะ React เองมีหน้าที่หลักคือ render UI ตาม state และ props แต่บางครั้งเราต้องทำงานที่ไม่ใช่แค่การ render เช่น:

- ติดต่อกับ ระบบภายนอก (API, localStorage, database)

- จัดการ เวลา (setInterval, setTimeout)

- จัดการ event ที่อยู่นอก React (เช่น window resize, scroll listener)

- ทำ cleanup (ยกเลิก subscription, clear timer)

สิ่งเหล่านี้คือ "side effects" เพราะมันมีผลกระทบต่อโลกภายนอกของ React component

### ตัวอย่างง่าย: Timer Component

```javascript
import { useEffect, useState } from "react";

function Timer() {
  const [count, setCount] = useState(0);

  // 1) state ตั้งต้นคือ 0

  useEffect(() => {
    console.log("Component mount - เริ่มต้นง timer");

    // 2) ตั้ง interval ให้นับทุก 1000 milliseconds (1 วินาที)
    const timer = setInterval(() => {
      setCount((c) => c + 1); // 3) เพิ่ม count ทีละ 1
    }, 1000);

    // 4) cleanup: ปลดปล่อย interval เมื่อ component ถูก unmount
    return () => {
      console.log("Component unmount - ปิด timer");
      clearInterval(timer);
    };
  }, []); // 5) [] = ตั้ง timer เพียง 1 ครั้ง

  // 6) render หน้าจอ
  return <h1>Timer: {count} วินาที</h1>;
}
```

**ขั้นตอนการทำงาน**:

| ขั้น | เวลา          | เหตุการณ์                  | Output                                         |
| ---- | ------------- | -------------------------- | ---------------------------------------------- |
| 1)   | 0 วินาที      | Component mount            | `<h1>Timer: 0</h1>` console: "Component mount" |
| 2)   | 1 วินาที      | setInterval รัน ครั้งที่ 1 | `<h1>Timer: 1</h1>`                            |
| 3)   | 2 วินาที      | setInterval รัน ครั้งที่ 2 | `<h1>Timer: 2</h1>`                            |
| 4)   | 3 วินาที      | setInterval รัน ครั้งที่ 3 | `<h1>Timer: 3</h1>`                            |
| 5)   | ผู้ใช้ปิดหน้า | Component unmount          | console: "Component unmount" timer ปิด         |

**ประเด็นสำคัญ**:

```
useEffect(() => {
  // ทำใน Component mount (1 ครั้ง)
  const timer = setInterval(() => { ... }, 1000);

  // Cleanup: ทำตอน Component unmount (1 ครั้ง)
  return () => clearInterval(timer);
}, []); // [] = ทำผลงานนี้ 1 ครั้งเท่านั้น
```

### โครงสร้างพื้นฐาน

```javascript
import { useEffect } from "react";

export default function Component() {
  // 1) Component render ครั้งแรก

  useEffect(() => {
    // 3) หลังจาก render เสร็จ ให้ทำสิ่งนี้ (side effect)
    console.log("Component ถูก render");

    // Clean up (ไม่จำเป็น)
    return () => {
      // 2) ก่อนจะ unmount ให้ทำสิ่งนี้ (cleanup)
      console.log("Component จะถูก unmount");
    };
  }, []);
  // ↑ Dependency Array

  // 2) Return JSX ให้ render
  return <h1>Hello World</h1>;
}
```

**ลำดับการทำงาน**:

```
1) Component mount/render
   ↓
2) JSX render บนจอ
   ↓
3) useEffect รัน (หลังจาก render)
   ↓
4) Component ใช้งานปกติ
   ↓
5) ถ้าผู้ใช้ปิดหน้า → Cleanup function รัน
```

### Dependency Array - ตรวจสอบการเปลี่ยนแปลง

#### กรณี 1: [] (empty array) - ทำงาน 1 ครั้ง

**หมายความว่า**: useEffect จะทำงานเพียง **1 ครั้งเท่านั้น** เมื่อ Component ถูก mount (เพิ่มเข้ากับ DOM)

```javascript
useEffect(() => {
  console.log("ทำงาน 1 ครั้ง เมื่อ Component mount");
}, []); // ← [] = ไม่มี dependencies = ทำงาน 1 ครั้ง
```

**เมื่อไหร่ที่ได้ใช้**:

- ดึงข้อมูล API เครื่องแรก
- ตั้งค่า initial state
- Subscribe event listeners

#### กรณี 2: ไม่มี Dependency Array - ทำงานทุกครั้ง

**หมายความว่า**: useEffect จะทำงาน **ทุกครั้ง** ที่ Component render (รวม render ครั้งแรกด้วย)

```javascript
useEffect(() => {
  console.log("ทำงานทุกครั้ง render");
});
// ← ไม่มี dependency array = ทำงานทุกครั้ง
```

**ระวัง**: อาจเกิด **infinite loop** ได้ง่าย!

**ตัวอย่าง infinite loop**:

```javascript
function Bad() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("render");
    setCount(count + 1); // ← ทำให้ state เปลี่ยน → re-render → useEffect รันอีก → infinite!
  }); // ← ไม่มี dependencies
}

// 🔄 Infinite loop:
// render → setCount(1) → re-render → render → setCount(2) → re-render → ...
```

#### กรณี 3: [variable] - ทำงานเมื่อ variable เปลี่ยน

**หมายความว่า**: useEffect จะทำงานเมื่อ **variable ในArray เปลี่ยนค่า**

```javascript
useEffect(() => {
  console.log(`count เปลี่ยนเป็น: ${count}`);
}, [count]); // ← [count] = เมื่อ count เปลี่ยน จึงทำงาน
```

**ลำดับการทำงาน**:

```
1) Component mount (count=0) → useEffect รัน → log "count = 0"
2) Click (count เปลี่ยน) → useEffect รัน → log "count = 1"
3) Click (count เปลี่ยน) → useEffect รัน → log "count = 2"
4) Other state เปลี่ยน → useEffect ไม่รัน (count ไม่เปลี่ยน)
```

**เมื่อไหร่ที่ได้ใช้**:

- ดึงข้อมูลใหม่เมื่อ keyword ค้นหาเปลี่ยน
- อัปเดต title เมื่อ page เปลี่ยน
- ทำ validation เมื่อ email เปลี่ยน

**ตัวอย่าง**:

```javascript
function SearchUsers() {
  const [search, setSearch] = useState("");
  const [users, setUsers] = useState([]);

  useEffect(() => {
    // 1) ดึงข้อมูลผู้ใช้
    console.log(`🔍 ค้นหา: ${search}`);

    // 2) ... fetch API ...
  }, [search]); // ← [search] = เมื่อ search เปลี่ยน จึงรัน

  return (
    <div>
      {/* ผู้ใช้พิมพ์ลงใน input */}
      <input
        onChange={(e) => setSearch(e.target.value)} // ← search เปลี่ยน → useEffect รัน
      />
      {/* แสดงผลลัพธ์ */}
      {users.map((user) => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}
```

---

## Fetching Data - เรียก API

### Fetch API คืออะไร

**Fetch API** คือ API ที่ใช้ **ดึงข้อมูลจากเซิร์ฟเวอร์** โดยไม่ต้องรีเฟรชหน้าเว็บ (asynchronous)

```javascript
fetch(url) // ส่งคำขอไปยังเซิร์ฟเวอร์
  .then((response) => response.json()) // แปลง response เป็น JSON
  .then((data) => console.log(data)) // ใช้ข้อมูล
  .catch((error) => console.log(error)); // จับข้อผิดพลาด
```

หรือใช้ **async/await** (สั้นกว่า):

```javascript
async function fetchData() {
  const response = await fetch(url);
  const data = await response.json();
  console.log(data);
}
```

### async/await คืออะไร

- **async** = ฟังก์ชันที่อาจใช้เวลา (ไม่ทำงานทันที)
- **await** = รอให้ Promise เสร็จ แล้วค่อยไปบรรทัดถัดไป

**สำคัญ**: ต้องประกาศ `async` ก่อนจึงจะใช้ `await` ได้!

**ตัวอย่าง async/await**:

```javascript
// ผิด: useEffect ไม่สามารถเป็น async ได้
useEffect(async () => {  // Error!
  const data = await fetch(...);
}, []);

// ถูก: สร้าง async function ข้างใน
useEffect(() => {
  async function fetchData() {  // ถูก
    const data = await fetch(...);
  }
  fetchData();
}, []);
```

**ลำดับกับ async/await**:

```
async function fetchData() {
  1) const response = await fetch(url);  // รอ API response (อาจ 1-5 วินาที)
  2) const data = await response.json(); // รอแปลง JSON
  3) setUser(data);                      // set state
}

// เมื่อเรียก fetchData():
// → รอ 1) → รอ 2) → ทำ 3) → เสร็จ
```

**ความแตกต่าง .then() กับ async/await**:

```javascript
// .then() style (เก่า)
fetch(url)
  .then((res) => res.json())
  .then((data) => setUser(data))
  .catch((err) => setError(err));

// async/await style (ใหม่)
async function get() {
  try {
    const res = await fetch(url);
    const data = await res.json();
    setUser(data);
  } catch (err) {
    setError(err);
  }
}
```

## วิธีเรียกข้อมูลจาก REST API โดยใช้ fetch ร่วมกับ useEffect และจัดการข้อผิดพลาดด้วย async/await

### ตัวอย่าง 1: ดึงข้อมูลจาก JSONPlaceholder

สิ่งสำคัญ:

- ใช้ **async function** ด้านในเพราะ useEffect ไม่สามารถเป็น async ได้โดยตรง
- ใช้ **try/catch/finally** เพื่อจัดการข้อผิดพลาด และตั้งค่า loading
  - `try` = ดึงข้อมูล
  - `catch` = จับข้อผิดพลาด
  - `finally` = ปิด loading (ว่าสำเร็จหรือล้มเหลว)

```javascript
import { useEffect, useState } from "react";

export default function UserProfile() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // ประกาศ async function ด้านใน useEffect
    async function fetchUser() {
      try {
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/users/1",
        );

        // ตรวจสอบว่าการดึงข้อมูลสำเร็จหรือไม่
        // - response.ok = true ถ้า status code เป็น 200-299
        if (!response.ok) {
          throw new Error("ไม่สามารถดึงข้อมูลได้");
        }

        // convert JSON text เป็น JavaScript object
        const data = await response.json();
        setUser(data); // เซทผู้ใช้
        setError(null); // ล้างข้อผิดพลาด
      } catch (err) {
        // ถ้าเกิดข้อผิดพลาด
        setError(err.message);
        setUser(null);
      } finally {
        // 🏁 เสร็จแล้ว ปิด loading (ทั้งกรณี success และ error)
        setLoading(false);
      }
    }

    fetchUser(); // เรียกใช้ฟังก์ชัน
  }, []); // []  = ดึงข้อมูล 1 ครั้งเมื่อ Component mount เท่านั้น

  // � ลำดับการทำงาน:
  // 1️⃣ Component mount → loading=true
  // 2️⃣ fetch() รัน → รอ API response → รอ response.json()
  // 3️⃣ Success → setUser(data), setError(null), setLoading(false)
  // 4️⃣ Fail → setError(msg), setUser(null), setLoading(false)
  // 5️⃣ Render JSX ตาม state

  // �🔄 แสดง Loading
  if (loading) {
    return (
      <div style={{ padding: "20px", textAlign: "center" }}>
        <p>⏳ กำลังโหลด...</p>
      </div>
    );
  }

  // แสดง Error
  if (error) {
    return (
      <div
        style={{
          padding: "20px",
          backgroundColor: "#f8d7da",
          color: "#721c24",
          borderRadius: "4px",
        }}
      >
        <p>❌ ข้อผิดพลาด: {error}</p>
      </div>
    );
  }

  // ✅ แสดง Data (เมื่อสำเร็จ)
  return (
    <div
      style={{
        border: "1px solid #ddd",
        padding: "20px",
        borderRadius: "8px",
        maxWidth: "500px",
        margin: "20px auto",
      }}
    >
      <h2>👤 {user?.name}</h2>
      <p>
        <strong>Email:</strong> {user?.email}
      </p>
      <p>
        <strong>Phone:</strong> {user?.phone}
      </p>
      <p>
        <strong>Website:</strong> {user?.website}
      </p>
      <p>
        <strong>Company:</strong> {user?.company?.name}
      </p>
    </div>
  );
}
```

### ตัวอย่าง 2: ดึงรายการข้อมูล

```javascript
import { useEffect, useState } from "react";

export default function PostList() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchPosts() {
      try {
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/posts?_limit=10",
        );
        const data = await response.json();
        setPosts(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }

    fetchPosts();
  }, []);

  if (loading) return <p>⏳ กำลังโหลด...</p>;
  if (error) return <p>❌ ข้อผิดพลาด: {error}</p>;

  return (
    <div style={{ maxWidth: "600px", margin: "20px auto" }}>
      <h1>บทความทั้งหมด</h1>
      {posts.map((post) => (
        <div
          key={post.id}
          style={{
            border: "1px solid #ddd",
            padding: "15px",
            marginBottom: "15px",
            borderRadius: "4px",
          }}
        >
          <h3>{post.title}</h3>
          <p>{post.body}</p>
          <small>ID: {post.id}</small>
        </div>
      ))}
    </div>
  );
}
```

### ตัวอย่าง 3: ดึงข้อมูลตามเงื่อนไข

```javascript
import { useEffect, useState } from "react";

export default function UserSelector() {
  const [userId, setUserId] = useState(1);
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    setLoading(true);

    async function fetchUser() {
      try {
        const response = await fetch(
          `https://jsonplaceholder.typicode.com/users/${userId}`,
        );
        const data = await response.json();
        setUser(data);
      } finally {
        setLoading(false);
      }
    }

    fetchUser();
  }, [userId]); // ดึงข้อมูลใหม่เมื่อ userId เปลี่ยน

  return (
    <div
      style={{
        maxWidth: "500px",
        margin: "20px auto",
        padding: "20px",
        border: "1px solid #ddd",
        borderRadius: "8px",
      }}
    >
      <h2>เลือกผู้ใช้</h2>

      {/* Dropdown */}
      <select
        value={userId}
        onChange={(e) => setUserId(Number(e.target.value))}
        style={{
          padding: "10px",
          fontSize: "16px",
          marginBottom: "20px",
          width: "100%",
        }}
      >
        {[1, 2, 3, 4, 5].map((id) => (
          <option key={id} value={id}>
            ผู้ใช้ {id}
          </option>
        ))}
      </select>

      {/* Loading */}
      {loading && <p>⏳ กำลังโหลด...</p>}

      {/* User Info */}
      {!loading && user && (
        <div
          style={{
            backgroundColor: "#f0f0f0",
            padding: "15px",
            borderRadius: "4px",
          }}
        >
          <h3>{user.name}</h3>
          <p>📧 {user.email}</p>
          <p>📱 {user.phone}</p>
          <p>{user.company?.name}</p>
        </div>
      )}
    </div>
  );
}
```

---

## Loading + Error States

วิธีจัดการ 3 สถานะ (Loading, Success, Error) เมื่อดึงข้อมูลจาก API เพื่อแสดงการบอกใจผู้ใช้ว่า API กำลังทำงาน หรือเกิดข้อผิดพลาด

### Pattern: Loading, Success, Error

**ลำดับการทำงาน**:

- ชุด state 3 ตัว: loading, error, data
- แสดง UI ต่างกัน ตามสถานะ

```javascript
import { useEffect, useState } from "react";

export default function DataFetch() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true); // เริ่มต้นเป็น true
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchData() {
      try {
        setLoading(true); // เริ่มโหลด
        const res = await fetch("/api/data");

        if (!res.ok) throw new Error("Request failed");

        const json = await res.json();
        setData(json); // Success
        setError(null); // ล้างข้อผิดพลาด
      } catch (e) {
        setError(e.message); // Error
        setData(null); // ล้างข้อมูล
      } finally {
        setLoading(false); // หยุดโหลด (ไม่ว่า success/error)
      }
    }

    fetchData();
  }, []);

  // ลำดับการทำงาน:
  // 1) Component mount → loading=true, error=null, data=null
  // 2) fetch() → รอ API
  // 3) Success: data=json, error=null, loading=false
  //    หรือ
  //    Fail: data=null, error='msg', loading=false
  // 4) Render JSX ตามสถานะ

  // Template: ตรวจสอบ loading ก่อน
  if (loading) {
    return (
      <div style={{ padding: "20px", textAlign: "center" }}>
        ⏳ กำลังโหลด...
      </div>
    );
  }

  if (error) {
    return (
      <div
        style={{
          padding: "20px",
          backgroundColor: "#f8d7da",
          color: "#721c24",
          borderRadius: "4px",
        }}
      >
        ❌ {error}
      </div>
    );
  }

  return <div>✅ {JSON.stringify(data)}</div>;
}
```

### ตัวอย่าง: การแสดง State ต่างๆ

```javascript
import { useState } from "react";

export default function StatusDisplay() {
  const [status, setStatus] = useState("idle");

  async function handleFetch() {
    setStatus("loading");

    try {
      // จำลองการโหลด
      await new Promise((resolve) => setTimeout(resolve, 2000));

      // 50/50 chance of success/error
      if (Math.random() > 0.5) {
        setStatus("success");
      } else {
        setStatus("error");
      }
    } catch (e) {
      setStatus("error");
    }
  }

  return (
    <div style={{ padding: "20px" }}>
      <button onClick={handleFetch}>ดึงข้อมูล</button>

      {/* Display Status */}
      <div style={{ marginTop: "20px", fontSize: "18px" }}>
        {status === "idle" && <p>กดปุ่มเพื่อเริ่ม</p>}

        {status === "loading" && (
          <p style={{ color: "#ffc107" }}>กำลังโหลด...</p>
        )}

        {status === "success" && <p style={{ color: "#28a745" }}>สำเร็จแล้ว</p>}

        {status === "error" && (
          <p style={{ color: "#dc3545" }}>เกิดข้อผิดพลาด</p>
        )}
      </div>
    </div>
  );
}
```

---

## Cleanup Function

วิธีการ Cleanup side effects เช่น ลบ Event Listener, ปิด Timer หรือยกเลิก Subscription เพื่อป้องกัน Memory Leak

### Cleanup Function คืออะไร

**Cleanup Function** คือ:

- ฟังก์ชันที่ทำ ก่อนที่ Component ถูก unmount หรือ dependencies มีการเปลี่ยนแปลง
- รันหลังจาก effect และก่อน effect ครั้งต่อไป (หากมี)

**ตัวอย่าง**:

```javascript
useEffect(() => {
  // side effect
  const timer = setInterval(() => { ... }, 1000);

  // cleanup function: ข้างใน return
  return () => {
    // Cleanupตรงนี้
    clearInterval(timer);
  };
}, []);
```

### ❓ ทำไมต้องใช้ Cleanup?

**Memory Leak** = หน่วยความจำที่ถูกครอบครอง แต่ไม่ถูกปล่อยออกมา

**ตัวอย่างเสี่ยง** (ไม่มี cleanup):

```javascript
function Bad() {
  useEffect(() => {
    const timer = setInterval(() => console.log("ทำงาน"), 1000);
    // ❌ ไม่มี cleanup = timer ยังวิ่งอยู่แม้ component ถูก unmount!
  }, []);
}
```

**ตัวอย่างแก้ไข** (มี cleanup):

```javascript
function Good() {
  useEffect(() => {
    const timer = setInterval(() => console.log("ทำงาน"), 1000);
    // ✅ cleanup: ปิด timer เมื่อ component ถูก unmount
    return () => clearInterval(timer);
  }, []);
}
```

### ตัวอย่าง 1: ลบ Event Listener

**สถานการณ์**: ท่านต้องการติดตามการคลิกผู้ใช้ แต่ต้อง "Cleanup" listener เมื่อ Component ถูก unmount

```javascript
import { useEffect } from "react";

export default function ClickCounter() {
  useEffect(() => {
    let count = 0;

    function handleClick() {
      count++;
      console.log("คลิก:", count);
    }

    // 1) เพิ่ม listener
    document.addEventListener("click", handleClick);

    // 2) Cleanup: ลบ listener เมื่อ Component unmount
    return () => {
      console.log("ลบ listener - ป้องกัน memory leak");
      document.removeEventListener("click", handleClick);
    };
  }, []); // 3) [] = add/remove 1 ครั้งเท่านั้น

  return <h1>ลองคลิกที่หน้าจอ (ดูที่ Console)</h1>;
}

// ลำดับการทำงาน:
// 1) Component mount
// 2) addEventListener("click") → listener เริ่มทำงาน
// 3) ผู้ใช้คลิก → count++, log "คลิก: 1"
// 4) ผู้ใช้คลิก → count++, log "คลิก: 2"
// 5) Component unmount
// 6) removeEventListener() → ปลดปล่อยหน่วยความจำ
```

### ตัวอย่าง 2: ยกเลิก Timer

**สถานการณ์**: Timer นับทุก 1 วินาที แต่ต้อง "หยุด" Timer เมื่อ Component ถูก unmount

```javascript
import { useEffect, useState } from "react";

export default function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    console.log("เริ่ม timer");

    // 1) ตั้งค่า interval ให้นับทุก 1000 ms
    const interval = setInterval(() => {
      setSeconds((prev) => prev + 1);
    }, 1000);

    // 2) Cleanup: ยกเลิก (clearInterval) เมื่อ Component unmount
    return () => {
      console.log("ปิด timer - ที่ " + seconds + " วินาที");
      clearInterval(interval);
    };
  }, []); // 3) [] = start/stop 1 ครั้งเท่านั้น

  return (
    <div style={{ textAlign: "center", padding: "20px" }}>
      <h1>Timer: {seconds} วินาที</h1>
      <p>Timer นับเวลาตั้งแต่ Component mount</p>
    </div>
  );
}

// ลำดับการทำงาน:
// 1) Component mount → log "เริ่ม timer"
// 2) setInterval ทำงาน → seconds 0 → 1 → 2 → 3 ... (ทุก 1 วินาที)
// 3) แสดง <h1>Timer: 0 วินาที</h1>
// 4) 1 วินาที → re-render → <h1>Timer: 1 วินาที</h1>
// 5) 2 วินาที → re-render → <h1>Timer: 2 วินาที</h1>
// 6) ผู้ใช้ปิดหน้า (Component unmount)
// 7) Cleanup: clearInterval(interval) → log "ปิด timer - ที่ 5 วินาที"
```

---

## ตัวอย่างจริง: Fetch Data + Display in React

สร้าง Component ที่:

1. ดึงข้อมูลจาก REST API (JSONPlaceholder)
2. แสดง Loading state
3. แสดง Error state (ถ้ามีปัญหา)
4. แสดงข้อมูลเมื่อสำเร็จ

### ตัวอย่าง: ระบบแสดงผลข้อมูล

```javascript
import { useEffect, useState } from "react";

export default function App() {
  const [posts, setPosts] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [searchTerm, setSearchTerm] = useState("");

  useEffect(() => {
    async function fetchPosts() {
      try {
        setLoading(true);
        const response = await fetch(
          "https://jsonplaceholder.typicode.com/posts?_limit=12",
        );

        if (!response.ok) throw new Error("Failed to fetch");

        const data = await response.json();
        setPosts(data);
        setError(null);
      } catch (err) {
        setError(err.message);
        setPosts([]);
      } finally {
        setLoading(false);
      }
    }

    fetchPosts();
  }, []);

  // Filter posts
  const filteredPosts = posts.filter((post) =>
    post.title.toLowerCase().includes(searchTerm.toLowerCase()),
  );

  return (
    <div style={{ maxWidth: "900px", margin: "0 auto", padding: "20px" }}>
      <h1>บทความ</h1>

      {/* Search */}
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="ค้นหาบทความ..."
        style={{
          width: "100%",
          padding: "10px",
          marginBottom: "20px",
          fontSize: "16px",
          borderRadius: "4px",
          border: "1px solid #ddd",
        }}
      />

      {/* Loading */}
      {loading && (
        <div style={{ textAlign: "center", padding: "40px" }}>
          <p style={{ fontSize: "18px" }}>กำลังโหลดบทความ...</p>
        </div>
      )}

      {/* Error */}
      {error && (
        <div
          style={{
            backgroundColor: "#f8d7da",
            color: "#721c24",
            padding: "20px",
            borderRadius: "4px",
            marginBottom: "20px",
          }}
        >
          <p>ข้อผิดพลาด: {error}</p>
        </div>
      )}

      {/* Posts */}
      {!loading && !error && (
        <div>
          <p style={{ color: "#666" }}>
            แสดง {filteredPosts.length} จาก {posts.length} บทความ
          </p>
          {filteredPosts.map((post) => (
            <div
              key={post.id}
              style={{
                border: "1px solid #ddd",
                padding: "20px",
                marginBottom: "15px",
                borderRadius: "4px",
                backgroundColor: "#f9f9f9",
              }}
            >
              <h3>{post.title}</h3>
              <p>{post.body.substring(0, 150)}...</p>
              <small>Post ID: {post.id}</small>
            </div>
          ))}

          {filteredPosts.length === 0 && (
            <p style={{ textAlign: "center", color: "#999" }}>
              ไม่พบบทความที่ตรงกับการค้นหา
            </p>
          )}
        </div>
      )}
    </div>
  );
}
```

---

## Best Practices: AbortController

**ปัญหา**: ถ้า Component unmount ระหว่างที่ fetch ยังไม่เสร็จ อาจจะ set state บน unmounted component = ⚠️ Memory Leak Warning

**วิธีแก้**: ใช้ **AbortController** เพื่อ cancel fetch request

```javascript
import { useEffect, useState } from "react";

export default function SafeFetch() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // สร้าง AbortController เพื่อ cancel request
    const controller = new AbortController();
    const signal = controller.signal;

    async function fetchData() {
      try {
        const response = await fetch("https://api.example.com/data", {
          signal, // ส่ง signal ไป
        });
        const json = await response.json();
        setData(json); // ปลอดภัยถ้า component ยังมี
      } catch (err) {
        // ถ้า signal ถูก abort จะ throw AbortError
        if (err.name !== "AbortError") {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    }

    fetchData();

    // Cleanup: ยกเลิก fetch เมื่อ component unmount
    return () => controller.abort();
  }, []);

  if (loading) return <p>กำลังโหลด...</p>;
  if (error) return <p>ข้อผิดพลาด: {error}</p>;
  return <p>สำเร็จ: {JSON.stringify(data)}</p>;
}
```

---

## Advanced Pattern: React Query / SWR

### ปัญหาที่ต้องจัดการ

ถ้าใช้ useEffect + fetch เปล่า ๆ จะมีปัญหา:

1. **Duplicate fetch** - ถ้า Component render หลายครั้ง fetch ก็หลายครั้ง
2. **Stale data** - ข้อมูลเก่า ไม่ได้ update
3. **Race condition** - Request เก่ามาหลัง request ใหม่
4. **Memory leak** - ลืม cleanup

### "Smart" Solution: React Query

ไลบรารี **React Query** (v3+) หรือ **TanStack Query** ช่วยจัดการทั้งหมด:

```bash
npm install @tanstack/react-query
```

**ตัวอย่าง**:

```javascript
import { useQuery } from "@tanstack/react-query";

export default function MyComponent() {
  // Automatic caching, deduplication, retry, เป็นต้น
  const { data, isLoading, error } = useQuery({
    queryKey: ["users"], // Key เพื่อ cache
    queryFn: async () => {
      const res = await fetch("/api/users");
      return res.json();
    },
  });

  return (
    <>
      {isLoading ? "กำลังโหลด..." : <p>{data?.name}</p>}
      {error && <p>ข้อผิดพลาด: {error.message}</p>}
    </>
  );
}
```

**ประโยชน์**:

- Automatic caching
- Deduplication (fetch 1 ครั้งจริง ๆ แม้เรียกหลายครั้ง)
- Built-in error/loading/success states
- ไม่ต้อง manual cleanup
- Background refetch แบบอัตโนมัติ

---

## Common Conceptual Mistakes

### 1. ลืม Dependency Array - Infinite Loop

**ปัญหา**: เมื่อไม่ใส่ dependency array useEffect จะทำงานทุกครั้ง render

```javascript
// ❌ ผิด: ไม่มี dependency array
useEffect(() => {
  setCount(count + 1); // ← สาเหตุ infinite loop!
  // 1. render → setCount → re-render → useEffect → setCount → ...
}); // ← ไม่มี []
```

**ผลข้างเคียง**:

- Infinite loop - component render เรื่อย ๆ ไม่หยุด
- Browser ค้างหรือ crash
- API call ทำงานหลายครั้งโดยไม่จำเป็น

**วิธีแก้**: ใส่ dependency array ให้ถูกต้อง

```javascript
// ✅ ถูก: มี [] = ทำงาน 1 ครั้ง
useEffect(() => {
  setCount(count + 1);
}, []); // ← ตอนแรก count=0 จึง +1 = 1 แล้วหยุด
```

---

### 2. ใส่ Object/Array ใน Dependency - Race Condition

**ปัญหา**: object/array ใหม่ทุกครั้ง render แม้ค่าไม่เปลี่ยน

```javascript
// ❌ ผิด: ใส่ object ใน dependency
function Component() {
  const user = { id: 1, name: "John" }; // ← object ใหม่ทุกครั้ง render

  useEffect(() => {
    console.log("fetch user:", user);
  }, [user]); // ← เปรียบเทียบ reference ไม่ใช่ค่า
  // useEffect ทำงาน: ทุกครั้ง render!
}
```

**ผลข้างเคียง**:

- API call ทำงานเรื่อย ๆ
- ความช้า performance
- Race condition (request เก่าตอบหลัง request ใหม่)

**วิธีแก้**: ใช้ id หรือ value primitive แทน

```javascript
// ✅ ถูก: ใช้ id ที่เป็น number
function Component() {
  const userId = 1; // ← number เปรียบเทียบได้

  useEffect(() => {
    fetch(`/api/users/${userId}`);
  }, [userId]); // ← เปลี่ยนเมื่อ userId เปลี่ยนจริง ๆ
}
```

---

### 3. ลืม Cleanup Function - Memory Leak

**ปัญหา**: subscriptions, timers, listeners ไม่ถูกปลดปล่อย

```javascript
// ❌ ผิด: ลืม cleanup
useEffect(() => {
  const timer = setInterval(() => {
    console.log("tick");
  }, 1000);
  // ลืม return cleanup ← timer ยัง "วิ่ง" อยู่!
}, []);
```

**ผลข้างเคียง**:

- Memory leak - หน่วยความจำเพิ่มขึ้นเรื่อย ๆ
- Multiple listeners ทำงานซ้อน
- Browser ช้าลง

**วิธีแก้**: ใส่ cleanup function

```javascript
// ✅ ถูก: มี cleanup
useEffect(() => {
  const timer = setInterval(() => {
    console.log("tick");
  }, 1000);

  return () => {
    clearInterval(timer); // ← ปลดปล่อย timer
  };
}, []);
```

---

### 4. ใส่ async โดยตรง ใน useEffect

**ปัญหา**: useEffect ยอมรับ function ที่คืนค่า function, ไม่ใช่ Promise

```javascript
// ❌ ผิด: async โดยตรง
useEffect(async () => {
  // ← Error!
  const data = await fetch(url);
  setData(data);
}, []);
```

**วิธีแก้**: สร้าง async function ข้างใน useEffect

```javascript
// ✅ ถูก: async function ข้างใน
useEffect(() => {
  async function fetchData() {
    const response = await fetch(url);
    const data = await response.json();
    setData(data);
  }
  fetchData();
}, []);
```

---

### 5. เปรียบเทียบ Object/Array ใน Dependency ผิด

**ปัญหา**: `options: { a: 1 }` ถือเป็น object ใหม่ทุกครั้ง

```javascript
// ❌ ผิด: inline object ใน dependency
useEffect(() => {
  fetch(url, options);
}, [{ a: 1 }]); // ← object ใหม่ทุกครั้ง!
```

**วิธีแก้**: ย้าย object ออกมาหรือใช้ useMemo

```javascript
// ✅ ถูก 1: ย้ายออก
const options = { a: 1 };
useEffect(() => {
  fetch(url, options);
}, [options]); // ← object ไม่เปลี่ยน

// ✅ ถูก 2: useMemo
const options = useMemo(() => ({ a: 1 }), []);
useEffect(() => {
  fetch(url, options);
}, [options]);
```

---

## Quick Reference: Mistakes สรุป

| ปัญหา         | สาเหตุ             | ผลลัพธ์            | วิธีแก้           |
| ------------- | ------------------ | ------------------ | ----------------- |
| ไม่มี []      | render ทุกครั้ง    | Infinite loop      | ใส่ []            |
| object ใน []  | reference ใหม่     | API ทำงานเรื่อย    | ใช้ id หรือ value |
| ลืม cleanup   | resources ไม่ปล่อย | Memory leak        | return cleanup    |
| async โดยตรง  | ไม่ support        | Error              | async ข้างใน      |
| inline object | reference ใหม่     | Effect ทำงานเรื่อย | extract/useMemo   |

---

## สรุปสัปดาห์ที่ 13

| หัวข้อ         | สิ่งที่เรียนรู้                 |
| -------------- | ------------------------------- |
| **useEffect**  | จัดการ side effects หลัง render |
| **Dependency** | ตรวจสอบการเปลี่ยนแปลง           |
| **Fetching**   | เรียก API ด้วย fetch            |
| **States**     | Loading, Success, Error         |
| **Cleanup**    | Cleanup เมื่อ unmount           |
| **Advanced**   | AbortController, React Query    |

---

## REST API ที่สามารถใช้ทดสอบ

```
https://jsonplaceholder.typicode.com/

- /posts - บทความ (1-100)
- /users - ผู้ใช้ (1-10)
- /comments - ความเห็น
- /todos - รายการที่ต้องทำ
```

---

## สัปดาห์หน้า

ต่อไปเราจะเรียนรู้:

- **React Router** - routing basics
- **useContext** - state sharing ระหว่าง Components
- **Deployment** - ส่งแอป ไป Vercel/Netlify
