Lỗi gặp phải
Too many re-renders. React limits the number of renders to prevent an infinite loop.
React dừng hoạt động sau khi đạt đến giới hạn render — khoảng 25 chu kỳ trong môi trường development. Ứng dụng bị đóng băng, console tràn ngập cùng một thông báo lỗi, và đôi khi tab trình duyệt bị crash hoàn toàn. Hầu hết trường hợp đều do một component đang tự lên lịch re-render sau mỗi lần vẽ giao diện.
Nguyên nhân gốc rễ
Ba mẫu code sau gây ra hầu hết các trường hợp:
- Gọi state setter trực tiếp trong phần thân render — không phải bên trong handler hoặc effect
- Truyền một hàm đã được gọi thực thi vào event prop thay vì truyền tham chiếu hàm
- Một
useEffectcó dependency thay đổi sau mỗi lần render, tạo ra vòng lặp
Cách sửa 1: State setter bị gọi trong quá trình render
Đây là nguyên nhân phổ biến nhất. Setter kích hoạt, gây ra re-render, rồi setter lại kích hoạt tiếp. React sẽ dừng lại ở khoảng ~25 vòng lặp trước khi trình duyệt bị treo.
// ❌ Sai — setCount chạy sau mỗi lần render
function Counter() {
const [count, setCount] = useState(0);
setCount(count + 1); // gọi trực tiếp trong phần thân render
return <div>{count}</div>;
}
// ✅ Sửa — chuyển vào useEffect hoặc event handler
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // chạy sau khi mount, không phải trong quá trình render
}, []); // deps rỗng = chỉ chạy một lần
return <div>{count}</div>;
}
Cách sửa 2: Gọi hàm thay vì truyền hàm
Chỉ một cặp dấu ngoặc thừa là gây ra vấn đề này. onClick={handleClick()} gọi hàm ngay lập tức trong quá trình render. onClick={handleClick} truyền nó dưới dạng tham chiếu để React gọi sau, khi người dùng click.
// ❌ Sai — handleClick() thực thi trong quá trình render, không phải khi click
function MyButton() {
const [open, setOpen] = useState(false);
function handleClick() {
setOpen(true);
}
return <button onClick={handleClick()}>Open</button>;
}
// ✅ Sửa — truyền tham chiếu, không có dấu ngoặc
return <button onClick={handleClick}>Open</button>;
Cần truyền tham số? Hãy bọc nó trong arrow function:
// ✅ Arrow function — an toàn, không gọi thực thi khi render
return <button onClick={() => handleClick(someId)}>Open</button>;
Cách sửa 3: Vòng lặp dependency trong useEffect
Một effect thay đổi một trong các dependency của chính nó sẽ chạy mãi mãi. State thay đổi → effect chạy lại → state lại thay đổi. Phá vỡ vòng lặp này bằng cách loại bỏ biến state khỏi mảng dependency.
// ❌ Sai — items thay đổi → effect chạy → items thay đổi → lặp vô hạn
useEffect(() => {
setItems([...items, newItem]);
}, [items]);
// ✅ Sửa — functional updater đọc state mới nhất mà không cần đưa vào deps
useEffect(() => {
setItems(prev => [...prev, newItem]);
}, [newItem]); // chỉ chạy lại khi newItem thay đổi
Cách sửa 4: Object hoặc array inline làm dependency
Trường hợp này khá tinh vi. JavaScript tạo ra một tham chiếu object hoàn toàn mới sau mỗi lần render, ngay cả khi các giá trị bên trong giống hệt nhau. React dùng so sánh tham chiếu để kiểm tra dependency — vì vậy { id: 1 } viết inline luôn bị coi là "đã thay đổi".
// ❌ Sai — { id: 1 } là một tham chiếu mới sau mỗi lần render
useEffect(() => {
fetchData({ id: 1 });
}, [{ id: 1 }]);
// ✅ Sửa — ổn định bằng useMemo để tham chiếu không thay đổi giữa các lần render
const params = useMemo(() => ({ id: 1 }), []);
useEffect(() => {
fetchData(params);
}, [params]);
Cách sửa 5: setState có điều kiện không bao giờ dừng
Đặt state bên trong khối if trông có vẻ vô hại — nhưng khi điều kiện luôn đúng, nó sẽ kích hoạt sau mỗi lần render.
// ❌ Sai — điều kiện luôn đúng khi data tồn tại
function Form({ data }) {
const [value, setValue] = useState('');
if (data) {
setValue(data.name); // chạy mỗi lần render miễn là data có giá trị
}
return <input value={value} />;
}
// ✅ Sửa — chuyển việc đồng bộ vào useEffect với data làm dependency
function Form({ data }) {
const [value, setValue] = useState('');
useEffect(() => {
if (data) {
setValue(data.name);
}
}, [data]); // chỉ chạy khi data thực sự thay đổi
return <input value={value} />;
}
Cách xác định component nào đang bị lặp
Stack trace của React thường trỏ vào code nội bộ fiber, không phải file của bạn. Dùng các bước sau để xác định chính xác thủ phạm:
- Mở React DevTools → tab Profiler → nhấn Record → component đang lặp sẽ hiển thị 50–200 lần render trong chưa đầy một giây
- Thêm
console.count('MyComponent')vào đầu mỗi component nghi ngờ — cái nào in ra 100+ lần là thủ phạm - Comment từng
useEffectra một cho đến khi vòng lặp dừng — đó chính là hook gây ra vấn đề - Kiểm tra tất cả event prop (
onClick,onChange,onSubmit) xem có dấu()thừa sau tên handler không
Xác nhận đã sửa xong
- Lỗi không còn xuất hiện trong console
- React DevTools Profiler cho thấy component chỉ render khi có tương tác của người dùng hoặc khi component cha re-render — không tự render nữa
console.countngừng tăng sau lần mount đầu tiên- Tab Network xác nhận các API call chỉ gửi một lần mỗi hành động, không phát liên tục
Danh sách kiểm tra phòng tránh
- State setter phải nằm trong event handler hoặc effect — không bao giờ trong phần thân render
- Event prop nhận tham chiếu:
onClick={fn}, không phải lời gọi hàm:onClick={fn()} - Bên trong
useEffect, ưu tiên dùng functional updater (prev => ...) và bỏ biến state ra khỏi mảng dependency - Bọc các object và array dùng làm effect dependency bằng
useMemohoặcuseCallbackđể ổn định tham chiếu - Thêm
eslint-plugin-react-hooksvào dự án — ruleexhaustive-depssẽ phát hiện hầu hết các vấn đề này trước khi chúng xuất hiện trên trình duyệt

