Hai quy tắc: chỉ gọi hook ở cấp cao nhất của hàm, và chỉ gọi trong function component hoặc custom hook. Cả hai đều xuất phát từ một chi tiết cài đặt — React nhận diện hook theo thứ tự gọi, không theo tên.
React giữ một danh sách hook cho mỗi component. Lần render đầu, hook thứ nhất lấy ô nhớ số 1, hook thứ hai lấy ô số 2. Lần render sau, React lại đọc theo đúng thứ tự đó. Nếu số lượng hoặc thứ tự lời gọi thay đổi giữa hai lần render, các ô lệch nhau — và state của hook này rơi vào hook khác.
// Sai: hook nằm sau một lệnh return sớm
function Profile({ user }) {
if (!user) return null // lần render này bỏ qua useState bên dưới
const [tab, setTab] = useState('info')
...
}
// Đúng: hook chạy trước, điều kiện xử lý sau
function Profile({ user }) {
const [tab, setTab] = useState('info')
if (!user) return null
...
}Cùng lý do đó, hook không đặt được trong if, trong vòng lặp, hay trong hàm lồng bên trong component. Cần logic có điều kiện thì đưa điều kiện vào trong hook, đừng đặt hook vào trong điều kiện: useEffect(() => { if (enabled) run() }, [enabled]).
Quy tắc thứ hai gọn hơn: hook cần một component đang render để gắn state vào. Gọi từ hàm JavaScript thường hay từ class thì không có chỗ nào để gắn, và React ném lỗi "Invalid hook call".
Lưu ý: Cài eslint-plugin-react-hooks và để nó ở mức error. Lớp lỗi này khi chạy lên hiện ra dưới dạng state của hook này rơi sang hook khác giữa các lần render — rất khó lần từ triệu chứng ngược về nguyên nhân.