useEffect — Timer & Cleanup
useEffect — ทำงานเมื่อมีอะไรบางอย่างเกิดขึ้น
Section titled “useEffect — ทำงานเมื่อมีอะไรบางอย่างเกิดขึ้น”useEffect ใช้ทำ side effects — งานที่เกิดขึ้น “ข้างเคียง” การ render เช่น:
- เริ่มต้น timer
- ดึงข้อมูลจาก API
- แก้ไข DOM โดยตรง
- ตั้ง event listeners
รูปแบบการใช้
Section titled “รูปแบบการใช้”useEffect(() => { // ทำงานตอน mount (และทุกครั้งที่ dependency เปลี่ยน) return () => { // cleanup ตอน unmount (หรือก่อน run ใหม่) }}, [dependencies]) // array ของ dependencyDependency Array — เมื่อไหร่ควร run?
Section titled “Dependency Array — เมื่อไหร่ควร run?”useEffect(() => { // 1. ไม่มี dependency → run ทุกครั้ง}, )
useEffect(() => { // 2. empty array → run ครั้งเดียวตอน mount}, [])
useEffect(() => { // 3. มี dependency → run เมื่อค่าใน array เปลี่ยน}, [count, name])flowchart TB
A[render เสร็จ] --> B{dependency array?}
B -->|"ไม่มี []"| C[run ทุกครั้ง]
B -->|"[] (empty)"| D[run ครั้งเดียว]
B -->|"[a, b]"| E{ถ้า a หรือ b เปลี่ยน?}
E -->|ใช่| F[run ใหม่]
E -->|ไม่| G[ข้าม]
classDef run fill:#fbbf24,stroke:#f59e0b,stroke-width:2px
class C,D,F run
ตัวอย่าง: เปลี่ยน title ตาม count
Section titled “ตัวอย่าง: เปลี่ยน title ตาม count”import { useEffect, useState } from "react"
function TitleUpdater() { const [count, setCount] = useState(0)
useEffect(() => { document.title = `Count: ${count}` }, [count]) // run ใหม่เมื่อ count เปลี่ยน
return ( <button onClick={() => setCount(c => c + 1)}> +1 </button> )}ตัวอย่าง: เรียก function เมื่อ state เปลี่ยน
Section titled “ตัวอย่าง: เรียก function เมื่อ state เปลี่ยน”function ChatApp() { const [message, setMessage] = useState("")
useEffect(() => { if (message) { playSound() // เล่นเสียงเมื่อมี message ใหม่ } }, [message]) // run ใหม่เมื่อ message เปลี่ยน
return ( <input value={message} onChange={e => setMessage(e.target.value)} /> )}flowchart TD
A[User พิมพ์ข้อความ] --> B[Component รับค่า message]
B --> C{message เปลี่ยนไหม?}
C -- ใช่ --> D[run useEffect]
D --> E[เล่นเสียง 🔔]
C -- ไม่ --> F[ไม่ทำอะไร]
ตัวอย่าง: Timer ด้วย useEffect
Section titled “ตัวอย่าง: Timer ด้วย useEffect”ลองทำ counter ที่นับเวลาอัตโนมัติ:
import { useState, useEffect } from "react"
function TimerCounter() { const [count, setCount] = useState(0)
useEffect(() => { // สร้าง interval ทุก 1 วินาที const timer = setInterval(() => { setCount(c => c + 1) }, 1000)
// ❌ ถ้าไม่มี cleanup — timer จะทำงานต่อไปเรื่อย ๆ แม้ unmount แล้ว!
// ✅ cleanup: ลบ interval เมื่อ component ถูกทำลาย return () => clearInterval(timer) }, []) // empty array = run ครั้งเดียวตอน mount
return <div>Count: {count}</div>}เปรียบเทียบ: มี vs ไม่มี cleanup
Section titled “เปรียบเทียบ: มี vs ไม่มี cleanup”// ❌ ไม่มี cleanup — Memory Leak!function BadTimer() { const [count, setCount] = useState(0)
useEffect(() => { const timer = setInterval(() => { setCount(c => c + 1) }, 1000) // ไม่ return cleanup! }, [])
return <div>{count}</div>}
// ✅ มี cleanup — ปลอดภัย!function GoodTimer() { const [count, setCount] = useState(0)
useEffect(() => { const timer = setInterval(() => { setCount(c => c + 1) }, 1000)
return () => clearInterval(timer) }, [])
return <div>{count}</div>}⚠️ Pitfall: Infinity Loop
Section titled “⚠️ Pitfall: Infinity Loop” ถ้าใช้useEffect แล้วใส่ dependency เป็น field เดียวกับที่ setState ใน use effect จะเจอ loop นรกไม่รู้จบ ระวังด้วยหล่ะ
สาเหตุ: ใส่ state ใน dependency แล้วเปลี่ยนมันใน effect
Section titled “สาเหตุ: ใส่ state ใน dependency แล้วเปลี่ยนมันใน effect”// ❌ Infinity Loop! — อย่าทำแบบนี้!function BadCounter() { const [count, setCount] = useState(0)
useEffect(() => { setCount(count + 1) // เปลี่ยน count → render ใหม่ → run effect อีก → เปลี่ยน count อีก → ... }, [count]) // เพิ่ม count ใน dependency = ตายตัว!
return <div>{count}</div>}เกิดอะไรขึ้น:
useEffectrun เพราะcountอยู่ใน dependencysetCount(count + 1)เปลี่ยนcount- React render ใหม่
useEffectrun อีก… วนไปเรื่อยๆ 🔄
flowchart TD
A[useEffect run] --> B[setCount เปลี่ยน count]
B --> C[React render ใหม่]
C --> D[useEffect run อีก]
D --> B
style A fill:#ef4444,stroke:#dc2626,color:#fff
style B fill:#ef4444,stroke:#dc2626,color:#fff
style C fill:#ef4444,stroke:#dc2626,color:#fff
style D fill:#ef4444,stroke:#dc2626,color:#fff
แก้ไข: ใช้ functional update
Section titled “แก้ไข: ใช้ functional update”// ✅ ปลอดภัย! — ใช้ functional updatefunction GoodCounter() { const [count, setCount] = useState(0)
useEffect(() => { // ใช้ function แทนการอ่านค่า directly setCount(c => c + 1) }, []) // ไม่มี dependency ที่เปลี่ยนใน effect!
return <div>{count}</div>}
// หรือถ้าต้องการ run เมื่อบางอย่างเปลี่ยนfunction CounterWithTrigger() { const [count, setCount] = useState(0) const [trigger, setTrigger] = useState(0)
useEffect(() => { setCount(c => c + 1) }, [trigger]) // trigger เปลี่ยน → run แค่ครั้งเดียว
return ( <div> <p>Count: {count}</p> <button onClick={() => setTrigger(t => t + 1)}> +1 Trigger </button> </div> )}อีกกรณี: fetch แล้ว setState ใส่ dependency
Section titled “อีกกรณี: fetch แล้ว setState ใส่ dependency”// ❌ Infinity Loop!function BadFetch() { const [data, setData] = useState(null)
useEffect(() => { fetch('/api/data') .then(res => res.json()) .then(result => { setData(result) // เปลี่ยน data → effect run อีก! }) }, [data]) // เปลี่ยน data ใน dependency = วนตลอด!}
// ✅ แก้ไข: ใส่ empty array หรือใช้ flagfunction GoodFetch() { const [data, setData] = useState(null)
useEffect(() => { let mounted = true
fetch('/api/data') .then(res => res.json()) .then(result => { if (mounted) setData(result) })
return () => { mounted = false } }, []) // run ครั้งเดียวตอน mount
return <div>{data}</div>} จำไว้นะ ถ้าเจ้าใส่ state ใน dependency แล้วเปลี่ยน state นั้นใน effect = infinity loop แน่นอน! ใช้ `setCount(c => c + 1)` แทน `setCount(count + 1)` เสมอ หรือใส่ empty array `[]` แล้วใช้ cleanup เป็น flag ควบคุม