ข้ามไปยังเนื้อหา

Rules & Custom Hooks

Rules of Hooks มีแค่สองข้อ:

  1. เรียก hook ที่ top level เท่านั้น — ห้ามเรียกใน condition, loop หรือ nested function
  2. เรียก 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 แบบมีเงื่อนไขทำให้ slot ที่เหลือทั้งหมดเลื่อน

ถ้าคุณเรียก hook แบบมีเงื่อนไข พอถึง render ที่เงื่อนไขต่างออกไป จำนวนและลำดับ ของการเรียก hook จะเปลี่ยน — และ hook ทุกตัวหลังตัวที่มีเงื่อนไขจะอ่าน slot ผิด state รั่วข้าม hook วุ่นวายตามมา การเรียก hook แบบไม่มีเงื่อนไข ในลำดับเดิมทุก render คือสิ่งที่ทำให้การ map slot คงที่ นั่นคือเหตุผลทั้งหมดของกฎ

บังคับใช้อัตโนมัติด้วย lint rule ทางการ (eslint-plugin-react-hooks) — plugin นี้จับ hook แบบมีเงื่อนไขและ effect dependency ที่ขาดก่อน ship

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 ชัดขึ้น

Rules of Hooks สองข้อคืออะไร?
ทำไมเรียก hook แบบมีเงื่อนไขไม่ได้?
custom hook คืออะไร?
สอง component เรียก `useCounter()` ทั้งคู่ ทั้งสองแชร์อะไรกัน?