Vấn đềBạn đang nhìn chằm chằm vào một hàng dài những dòng chữ đỏ trong console? Cảnh báo "React has detected a change in the order of Hooks" không chỉ là một sự phiền toái—nó là một cơ chế bảo vệ để ngăn chặn việc hỏng dữ liệu. Lỗi này xảy ra khi trình tự các Hook bạn định nghĩa bị thay đổi giữa các lần render (kết xuất) khác nhau.
Warning: React has detected a change in the order of Hooks called by MyComponent.
React không theo dõi state (trạng thái) bằng tên. Thay vào đó, nó sử dụng một hệ thống con trỏ. Mỗi khi bạn gọi useState hoặc useEffect, React sẽ di chuyển một "con trỏ" qua một danh sách nội bộ các vị trí (slots). Nếu bạn gọi ba Hook ở lần render thứ nhất, nhưng chỉ gọi hai ở lần render thứ hai, con trỏ sẽ bị lệch. Điều này có thể khiến state "User Profile" (Hồ sơ người dùng) của bạn vô tình bị rò rỉ vào một nút gạt "Dark Mode" (Chế độ tối), dẫn đến các sự cố không thể lường trước.
Những trường hợp thường gây lỗi- Đặt Hook bên trong logic if hoặc switch.- Đặt Hook sau một lệnh return sớm (ví dụ: loading guard).- Lồng Hook bên trong vòng lặp map() hoặc for.- Định nghĩa Hook bên trong các hàm trợ giúp (helper functions) thay vì ở cấp cao nhất của component.## Các bước khắc phục### Trường hợp 1: Hook nằm trong khối 'if'Bạn có thể thử fetch (lấy) dữ liệu chỉ khi một ID cụ thể tồn tại. Điều này nghe có vẻ hợp lý, nhưng nó làm phá vỡ trình đăng ký nội bộ của React nếu ID đó bị null.
❌ Cách làm sai (Anti-pattern):
function UserProfile({ userId }) {
if (userId) {
// ❌ Lỗi! React chỉ thấy hook này trong một số trường hợp
useEffect(() => {
fetchUser(userId);
}, [userId]);
}
return <div>User Profile</div>;
}
✅ Cách triển khai đúng: Giữ Hook ở cấp cao nhất (top level) và di chuyển điều kiện kiểm tra vào bên trong callback của effect.
function UserProfile({ userId }) {
useEffect(() => {
if (!userId) return; // Logic nằm bên trong hook
fetchUser(userId);
}, [userId]);
return <div>User Profile</div>;
}
Trường hợp 2: Hook nằm sau lệnh return sớmCác hiệu ứng tải (loading spinner) thường gây ra vấn đề này. Nếu bạn return JSX sớm, bất kỳ Hook nào được định nghĩa bên dưới dòng đó sẽ không được thực thi. Điều này làm thay đổi số lượng Hook từ 0 thành 5 ngay khi dữ liệu được tải xong, gây ra lỗi ứng dụng.
❌ Cách làm sai (Anti-pattern):
function Dashboard({ data, loading }) {
if (loading) return <Spinner />;
// ❌ Lỗi! Hook này bị bỏ qua trong giai đoạn tải dữ liệu
const [activeTab, setActiveTab] = useState(0);
return <div>{data}</div>;
}
✅ Cách triển khai đúng: Di chuyển tất cả các khai báo state và effect lên trên cùng của hàm, trước bất kỳ câu lệnh if nào.
function Dashboard({ data, loading }) {
const [activeTab, setActiveTab] = useState(0); // Được định nghĩa ở cấp cao nhất (root)
if (loading) return <Spinner />;
return <div>{data}</div>;
}
Trường hợp 3: Hook nằm trong vòng lặpCác danh sách động thường khá phức tạp. Nếu hôm nay bạn có 10 mục nhưng ngày mai chỉ còn 8, bạn vừa xóa đi hai lần gọi Hook khỏi bộ nhớ của React.
❌ Cách làm sai (Anti-pattern):
function ItemList({ items }) {
return items.map(item => {
const [status] = useState('pending'); // ❌ Lỗi! Số lượng Hook thay đổi theo độ dài danh sách
return <li>{item.name} - {status}</li>;
});
}
✅ Cách triển khai đúng: Di chuyển state cụ thể của từng mục vào component riêng của nó. Mỗi component con sẽ tự quản lý Hook của chính mình một cách độc lập.
function ListItem({ name }) {
const [status] = useState('pending');
return <li>{name} - {status}</li>;
}
function ItemList({ items }) {
return (
<ul>
{items.map(item => (
<ListItem key={item.id} name={item.name} />
))}
</ul>
);
}

