Sửa lỗi cảnh báo 'React has detected a change in the order of Hooks'

intermediate⚛️ React2026-06-04| React 16.8+, Next.js, Vite, Create React App, Trình duyệt web (Chrome, Firefox, v.v.)

Error Message

Warning: React has detected a change in the order of Hooks called by ComponentName. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://reactjs.org/link/rules-of-hooks
#react#hooks#rules-of-hooks#gỡ lỗi#javascript

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>
  );
}

Kiểm tra giải pháp- Xóa sạch Console: Tải lại trang và kích hoạt trạng thái UI từng gây ra lỗi trước đó. Cảnh báo màu đỏ sẽ không còn xuất hiện.- Xác minh tính nhất quán của State: Chuyển đổi giữa các tab hoặc chế độ xem. Đảm bảo các giá trị trong useState không bị "nhảy" sang các biến khác.- Chạy ESLint: Thực hiện lệnh npm run lint. Hầu hết các cấu hình hiện đại sẽ phát hiện vi phạm react-hooks/rules-of-hooks trước khi bạn kịp nhấn lưu, giúp bạn tiết kiệm khoảng 15 phút gỡ lỗi cho mỗi lần gặp lỗi.## Mẹo phòng ngừa- Strict Mode: Luôn bật <React.StrictMode>. Trong môi trường phát triển (development), nó sẽ gọi component hai lần nhằm phát hiện ra các lỗi về thứ tự Hook này.- Quy tắc "Cấp cao nhất": Hãy hình dung component của bạn gồm hai phần. Phần 1 là "Thiết lập Hook" ở trên cùng. Phần 2 là "Logic và Rendering" ở bên dưới. Đừng bao giờ trộn lẫn chúng.

Related Error Notes