Lỗi Gặp Phải
Mở console trình duyệt lên và bạn sẽ thấy thông báo kiểu như này — thường lặp lại nhiều lần:
Warning: Cannot update a component (`App`) while rendering a different component (`Child`).
To locate the bad setState() call inside `Child`, follow the stack trace as described in
https://reactjs.org/link/setstate-in-render
Ứng dụng vẫn có thể chạy được. Nhưng React đang cảnh báo một điều thực sự nguy hiểm: một component con đang kích hoạt cập nhật state ở component cha ngay trong giai đoạn render. React 16.13 đã giới thiệu cảnh báo này. Trong chế độ concurrent của React 18, nó có thể leo thang thành lỗi crash thật sự.
Nguyên Nhân Gốc Rễ
Giai đoạn render của React phải là một phép tính thuần túy — không có side effect, không cập nhật state, không có gì thay đổi thế giới bên ngoài hàm. Khi Child gọi một prop callback làm thay đổi state của App trực tiếp bên trong phần thân render — không phải trong một event handler hay effect — nó lên lịch cập nhật state trong khi React vẫn đang render dở dang. Đó là vi phạm hợp đồng.
Ba kiểu code thường gây ra lỗi này nhất:
- Gọi hàm setter của component cha một cách vô điều kiện ở đầu component
- Gọi
setStatebên trong một render-phase callback được truyền qua prop - Một ref callback hoặc render prop kích hoạt cập nhật state một cách đồng bộ
Tái Hiện Lỗi
Đây là trường hợp tối giản kích hoạt cảnh báo:
// ❌ Bị lỗi — Child gọi onMount trong lúc render, không phải trong effect
function Child({ onMount }) {
onMount('Child is here'); // chạy trong quá trình render!
return <div>Child</div>;
}
function App() {
const [message, setMessage] = React.useState('');
return (
<div>
<p>{message}</p>
<Child onMount={setMessage} />
</div>
);
}
Mỗi lần App render, nó sẽ render Child. Child lập tức gọi setMessage — một state setter của App. Điều đó xếp hàng thêm một lần render nữa cho App. React phát hiện điều này giữa chừng và ghi lại cảnh báo. Trong trường hợp xấu nhất, điều này tạo ra vòng lặp render vô tận.
Cách Sửa Từng Bước
Cách 1: Chuyển lệnh gọi vào useEffect (cách sửa phổ biến nhất)
Để thông báo cho component cha khi component con mount hoặc một giá trị thay đổi, hãy đặt lệnh gọi vào bên trong useEffect:
// ✅ Đã sửa — side effect xảy ra sau khi render, không phải trong lúc render
function Child({ onMount }) {
React.useEffect(() => {
onMount('Child is here');
}, []); // deps rỗng = chạy một lần sau khi mount
return <div>Child</div>;
}
function App() {
const [message, setMessage] = React.useState('');
return (
<div>
<p>{message}</p>
<Child onMount={setMessage} />
</div>
);
}
useEffect chạy sau khi trình duyệt vẽ xong. Cập nhật state của component cha diễn ra ở chu kỳ tiếp theo — không xung đột render, không có cảnh báo.
Cách 2: Derive state thay vì đồng bộ hóa nó
Đôi khi cảnh báo này là dấu hiệu của code xấu. Nó cho thấy bạn đang đồng bộ state thủ công trong khi đáng ra chỉ cần derive từ props hoặc state hiện có. Hãy tự hỏi: liệu App đã có sẵn dữ liệu mà Child đang cố báo cáo lại chưa?
// ❌ Anti-pattern: child phản chiếu dữ liệu ngược lại parent qua callback trong lúc render
function Child({ value, onValueSeen }) {
onValueSeen(value); // gây ra cảnh báo
return <span>{value}</span>;
}
// ✅ Tốt hơn: parent sở hữu dữ liệu, child chỉ hiển thị nó
function App() {
const [value] = React.useState('hello');
// Không cần Child báo cáo lại — App đã có `value` rồi
return <Child value={value} />;
}
Cách 3: Chuyển state xuống component con
State mà chỉ một component cần không nên đặt ở component cha. Hãy đưa nó xuống:
// ❌ Parent giữ state mà chỉ child dùng
function App() {
const [isOpen, setIsOpen] = React.useState(false);
return <Modal isOpen={isOpen} onToggle={setIsOpen} />;
}
// ✅ Child tự quản lý state mở/đóng của mình
function Modal() {
const [isOpen, setIsOpen] = React.useState(false);
return (
<div>
<button onClick={() => setIsOpen(o => !o)}>Toggle</button>
{isOpen && <div>Modal content</div>}
</div>
);
}
Cách 4: Kiểm tra callback của các component bên thứ ba
Thư viện bên ngoài cũng có thể là thủ phạm. Data grid, thư viện form, và danh sách ảo hóa đôi khi gọi callback của bạn trong chu kỳ render của chúng. Một trường hợp phổ biến:
// ❌ onRowsChange kích hoạt trong chu kỳ render của DataGrid
<DataGrid
rows={rows}
onRowsChange={(newRows) => setRows(newRows)} // có thể kích hoạt trong lúc render
/>
// ✅ Bọc trong useCallback và kiểm tra tài liệu của thư viện
const handleRowsChange = React.useCallback((newRows) => {
setRows(newRows);
}, []);
<DataGrid rows={rows} onRowsChange={handleRowsChange} />
Một thư viện liên tục kích hoạt trong lúc render là lỗi của phía thư viện đó. Kiểm tra phiên bản mới hơn, hoặc trì hoãn cập nhật state bằng useEffect.
Chiến Lược Debug
Cảnh báo đã nêu rõ tên các component liên quan. Hãy tận dụng điều đó:
- Mở DevTools → Console
- Nhấp vào đường dẫn stack trace bên cạnh cảnh báo
- Tìm frame bên trong component
Childcủa bạn (bỏ qua phần React internals) - Dòng đó là nơi
setStatekích hoạt — kiểm tra xem nó có nằm trong event handler,useEffect, hay thân render thô không
Stack trace bị rút gọn? Hãy chạy bản dev build (npm start hoặc next dev). React chỉ phát ra cảnh báo này ở chế độ development — nó sẽ không xuất hiện trong bản production build.
Xác Nhận Đã Sửa Xong
Sau khi áp dụng bản sửa lỗi, hãy chạy qua checklist này:
- Hard-refresh trang (
Ctrl+Shift+R/Cmd+Shift+R) - Mở console — cảnh báo phải biến mất
- Đi qua luồng bị ảnh hưởng: mount, cập nhật, và unmount component con
- Trong React DevTools Profiler, xác nhận số lần render ổn định và không có cascading re-render
Một điều cần lưu ý với React 18: Strict Mode cố ý chạy effect hai lần khi mount trong chế độ development. Nếu bản sửa useEffect của bạn khiến setter của component cha kích hoạt hai lần, hãy thêm một cleanup flag:
React.useEffect(() => {
let active = true;
if (active) onMount('Child is here');
return () => { active = false; };
}, []);
Bài Học Rút Ra
- Render phải thuần túy. Đừng bao giờ gọi setState — trực tiếp hay qua prop callback — ở cấp độ trên cùng của component. Đó là việc của event handler và
useEffect. - Prop callback không phải event handler. Truyền
setFooqua prop không có nghĩa là gọi nó trong lúc render là an toàn. Component nhận prop mới kiểm soát thời điểm nó kích hoạt. - Hãy xem đây là lỗi, không phải cảnh báo. Chế độ concurrent của React 18 làm cho các side effect trong giai đoạn render trở nên không xác định. Một cảnh báo vô hại hôm nay có thể âm thầm làm hỏng state khi các tính năng concurrent được kích hoạt.
- Mặc định dùng useEffect cho thông báo lúc mount. Bất cứ khi nào component con cần thông báo cho component cha điều gì đó lúc mount,
useEffectvới mảng dependency rỗng là công cụ phù hợp.

