Vì React không biết tên hook bạn gọi — nó chỉ dựa vào thứ tự gọi.
Mỗi component instance giữ một danh sách trạng thái hook gắn trên fiber của nó; mỗi lần render, React đi qua danh sách đó theo đúng thứ tự và ghép ô thứ n cho lời gọi hook thứ n.
function Form({ showEmail }) {
const [name, setName] = useState('') // slot 0
if (showEmail) {
const [email, setEmail] = useState('') // slot 1 only on some renders
}
useEffect(() => {}, []) // slot 1 or 2 -> mismatch
}Khi showEmail đổi từ true sang false, lời gọi useEffect nhận ô vốn thuộc về useState của email. Hậu quả là state của hook này bị đọc nhầm sang hook khác, deps của effect so sánh sai, và React thường ném thẳng lỗi "Rendered fewer hooks than expected".
Từ đó suy ra các luật:
- Chỉ gọi hook ở cấp cao nhất của component hoặc custom hook — không trong điều kiện, vòng lặp,
try/catch, hay sau một lệnhreturnsớm. - Chỉ gọi hook từ function component hoặc custom hook (tên bắt đầu bằng
use), không gọi trong handler hay hàm thường.
Cách xử lý khi thật sự cần rẽ nhánh: luôn gọi hook rồi rẽ nhánh ở giá trị (useState vẫn gọi, chỉ dùng khi cần), hoặc tách thành component con để React coi mỗi nhánh là một cây riêng.