Rules & Custom Hooks
Rules of Hooks มีแค่สองข้อ:
- เรียก hook ที่ top level เท่านั้น — ห้ามเรียกใน condition, loop หรือ nested function
- เรียก hook จาก React function เท่านั้น — component หรือ hook อื่น ไม่ใช่ function ธรรมดา
function Profile({ userId }) { const [user, setUser] = useState(null); // ✅ top level
if (!userId) { const [error, setError] = useState(null); // ❌ conditional — breaks the rules }
for (const id of ids) { useEffect(() => { /* ... */ }); // ❌ in a loop — breaks the rules }}ทำไมกฎข้อแรกถึงมี
หัวข้อที่มีชื่อว่า “ทำไมกฎข้อแรกถึงมี”จำจาก intro ของโมดูล: React จับคู่ hook แต่ละตัวกับ memory slot ตามลำดับการเรียก ไม่ใช่ตามชื่อ useState ตัวแรกคือ slot 1, ตัวที่สองคือ slot 2 ทุก render
flowchart TB r1["Render 1: useState, useState, useEffect slots 1, 2, 3"] --> ok["consistent"] r2["Render 2: (condition false) useState, useEffect slots 1, 2"] --> bad["useEffect now reads slot 2 — the state of the wrong hook"]
ถ้าคุณเรียก hook แบบมีเงื่อนไข พอถึง render ที่เงื่อนไขต่างออกไป จำนวนและลำดับ ของการเรียก hook จะเปลี่ยน — และ hook ทุกตัวหลังตัวที่มีเงื่อนไขจะอ่าน slot ผิด state รั่วข้าม hook วุ่นวายตามมา การเรียก hook แบบไม่มีเงื่อนไข ในลำดับเดิมทุก render คือสิ่งที่ทำให้การ map slot คงที่ นั่นคือเหตุผลทั้งหมดของกฎ
บังคับใช้อัตโนมัติด้วย lint rule ทางการ (eslint-plugin-react-hooks) — plugin นี้จับ hook แบบมีเงื่อนไขและ effect dependency ที่ขาดก่อน ship
Custom hooks: การแชร์ stateful logic
หัวข้อที่มีชื่อว่า “Custom hooks: การแชร์ stateful logic”custom hook คือ function ที่ชื่อขึ้นต้นด้วย use และเรียก hook อื่น เท่านั้นเอง — ไม่มี API พิเศษ แต่ให้คุณแยกและ reuse stateful logic (ไม่ใช่ UI) ข้าม component
// A custom hook: reusable logic, its own state, calls built-in hooks.function useOnlineStatus() { const [isOnline, setIsOnline] = useState(navigator.onLine); useEffect(() => { const on = () => setIsOnline(true); const off = () => setIsOnline(false); window.addEventListener('online', on); window.addEventListener('offline', off); return () => { window.removeEventListener('online', on); window.removeEventListener('offline', off); }; }, []); return isOnline;}
// Any component can now use it:function StatusBar() { const isOnline = useOnlineStatus(); return <span>{isOnline ? '✅ Online' : '❌ Offline'}</span>;}สิ่งสำคัญที่ต้องเข้าใจ: custom hook แชร์ logic ไม่ใช่ state ทุก component ที่เรียก useOnlineStatus() ได้ state ของตัวเองอิสระ — hook เป็นสูตรที่รันใหม่ต่อ component ไม่ใช่ store ที่แชร์กัน (สำหรับ state ที่แชร์ข้าม component ให้ lift state up หรือใช้ context/external store — โมดูลถัดไป)
เมื่อไรควรแยก custom hook: เมื่อสอง component ต้องการ stateful behavior เดียวกัน หรือเมื่อ logic ของ component ซับซ้อนพอที่การตั้งชื่อ (useForm, useDebouncedValue, useFetch) ทำให้ component ชัดขึ้น