Phanh khẩn cấp
React vừa mới kéo phanh khẩn cấp. Lỗi này thường có nghĩa là component của bạn đang rơi vào một vòng lặp, render lại quá nhanh đến mức có thể làm treo trình duyệt nếu React không ngăn chặn. Bạn sẽ thấy một hàng dài văn bản màu đỏ trong console và giao diện người dùng có thể sẽ ngừng phản hồi.
Error: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect...
Tóm tắt cách sửa nhanh
- Thiếu mảng dependency: Nếu effect chỉ nên chạy một lần khi component được gắn (mount), hãy thêm
[]làm đối số thứ hai. - Vòng lặp phản hồi trạng thái (State): Bạn đang cập nhật state đồng thời là một dependency? Hãy chuyển sang sử dụng functional update:
setCount(c => c + 1)thay vìsetCount(count + 1). - Tham chiếu không ổn định: Nếu dependency của bạn là một object hoặc array được định nghĩa bên trong component, nó sẽ là một object "mới" sau mỗi lần render. Hãy bao bọc nó trong
useMemo. - Câu lệnh bảo vệ (Guard Clause): Bao bọc
setStatecủa bạn trong một câu lệnhifđể đảm bảo nó chỉ kích hoạt khi dữ liệu thực sự thay đổi.
Tại sao lỗi này xảy ra
Hãy coi lỗi này như một mạng lưới an toàn. React giám sát các chu kỳ render của bạn và can thiệp nếu phát hiện thấy hơn 50 lần cập nhật liên tiếp trong một giây. Trong useEffect, điều này hầu như luôn là một phản ứng dây chuyền vòng lặp: Render → Effect → Thay đổi State → Render lại → Effect.
1. Thiếu mảng dependency
Quên đối số thứ hai là một sai sót phổ biến. Nếu không có nó, effect sẽ chạy sau mỗi lần render. Nếu effect đó cập nhật state, nó sẽ ép buộc một lần render mới, điều này lại làm chạy effect một lần nữa. Đó là một chu kỳ không bao giờ kết thúc.
// ❌ SAI: Vòng lặp vô tận
useEffect(() => {
setUserData(data);
}); // Không có mảng ở đây!
2. Bẫy so sánh tham chiếu
JavaScript so sánh các object và array bằng tham chiếu, không phải bằng giá trị. Nói một cách đơn giản: [] === [] là false. Nếu bạn định nghĩa một object hằng số bên trong component và đưa nó vào mảng dependency, React sẽ coi đó là một dependency hoàn toàn mới mỗi khi component render.
// ❌ SAI: 'options' là một tham chiếu bộ nhớ mới sau mỗi lần render
const options = { theme: 'dark' };
useEffect(() => {
// Logic tại đây
}, [options]);
Cách khắc phục
Cách 1: Sử dụng Functional Update
Ngừng đưa state hiện tại vào mảng dependency chỉ để cập nhật nó. Bằng cách sử dụng functional update, bạn có thể loại bỏ hoàn toàn biến state khỏi danh sách dependency, từ đó phá vỡ vòng lặp.
// ❌ SAI: 'count' trong mảng gây ra vòng lặp
useEffect(() => {
const timer = setInterval(() => setCount(count + 1), 1000);
return () => clearInterval(timer);
}, [count]);
// ✅ TỐT: Không cần dependency 'count'
useEffect(() => {
const timer = setInterval(() => setCount(prev => prev + 1), 1000);
return () => clearInterval(timer);
}, []);
Cách 2: Ổn định Object với useMemo
Nếu một object bắt buộc phải là một dependency, hãy đảm bảo tham chiếu của nó không đổi qua các lần render. useMemo lưu trữ object đó để React chỉ thấy nó "thay đổi" khi các giá trị bên trong của nó thực sự thay đổi.
// ✅ TỐT: 'options' chỉ thay đổi nếu 'theme' thay đổi
const options = useMemo(() => ({
color: theme === 'dark' ? '#fff' : '#000'
}), [theme]);
useEffect(() => {
// Logic chỉ chạy khi theme cụ thể thay đổi
}, [options]);
Cách 3: So sánh thủ công
Trước khi bạn kích hoạt một cập nhật state, hãy kiểm tra xem bạn có thực sự cần thiết không. Câu lệnh bảo vệ đơn giản này ngăn chặn việc render lại không cần thiết dẫn đến treo ứng dụng.
useEffect(() => {
if (newId !== currentId) {
setCurrentId(newId);
}
}, [newId, currentId]);
Cách kiểm tra kết quả
- Theo dõi Console: Làm mới trang. Khối lỗi màu đỏ sẽ biến mất.
- Kiểm tra tab Network: Nếu effect của bạn thực hiện fetch dữ liệu, hãy đảm bảo bạn không thấy hàng tá yêu cầu giống hệt nhau được gửi đi mỗi giây. Một hoặc hai là bình thường; năm mươi là một lỗi.
- React Profiler: Sử dụng Profiler trong React DevTools. Nếu dòng thời gian "Commit" hiển thị một đường thẳng các cập nhật nhanh chóng cho một component, vòng lặp vẫn còn đó.
Tài liệu tham khảo thêm
- Tài liệu React: Đồng bộ hóa với Effect
- Tài liệu React: Functional Updates
- MDN Web Docs: So sánh bằng trong JS

