Khắc phục lỗi Minified React #185: Ngăn chặn vòng lặp vô hạn trong Production

intermediate⚛️ React2026-06-30| Các bản build Production của React (v16, v17, v18) chạy trên mọi trình duyệt hiện đại.

Error Message

Error: Minified React error #185; visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message.
#react#production#gỡ lỗi#javascript

Sự cố Production lúc 2 giờ sáng

Trời đã khuya, và công cụ giám sát lỗi của bạn vừa báo động đỏ. Người dùng đang báo cáo rằng dashboard bị đơ hoặc tệ hơn là trắng xóa hoàn toàn. Bạn mở console production với hy vọng tìm thấy một stack trace rõ ràng, nhưng tất cả những gì bạn nhận được là một đường link mơ hồ và khó chịu:

Error: Minified React error #185; visit https://reactjs.org/docs/error-decoder.html?invariant=185 for the full message.

React loại bỏ các thông báo lỗi chi tiết trong môi trường production để tiết kiệm từng kilobyte quý giá. Nếu bạn truy cập liên kết đó, trình giải mã sẽ tiết lộ sự thật: "Maximum update depth exceeded." Điều này có nghĩa là ứng dụng của bạn đang bị kẹt trong một vòng lặp vô tận. React có một cơ chế an toàn giúp ngắt cây component sau khi đạt đến giới hạn—thường là 50 bản cập nhật lồng nhau—để ngăn trình duyệt bị treo hoặc quá tải.

Tại sao vòng lặp lại xảy ra

Về bản chất, vấn đề là một vòng lặp phản hồi logic. Một lần render kích hoạt thay đổi state, điều này buộc một lần render khác diễn ra, và nó lại kích hoạt chính thay đổi state đó một lần nữa. Nó xảy ra rất nhanh—thường chạm ngưỡng 50 lần render chỉ trong một phần nhỏ của giây.

Dưới đây là những lỗi thường khiến các lập trình viên mắc bẫy:

  • Gán State trực tiếp: Gọi setState trực tiếp trong thân của một functional component.
  • Bẫy Dependency: Một hook useEffect cập nhật một phần state mà chính nó cũng đang theo dõi.
  • Thực thi ngay lập tức: Truyền onClick={handleClick()} thay vì onClick={handleClick}. Việc này thực thi hàm ngay trong giai đoạn render thay vì đợi sự kiện click.
  • Đồng bộ hóa Prop-State: Sử dụng getDerivedStateFromProps hoặc logic tương tự trong class components gây ra cập nhật trong mỗi chu kỳ.

Cách khắc phục nhanh: Tìm kim đáy bể

Debug mã nguồn đã bị thu gọn (minified code) là một cơn ác mộng. Nếu bạn đã tải Source Maps lên một instance riêng tư của Sentry hoặc Datadog, hãy sử dụng chúng. Chúng sẽ ánh xạ dòng code khó hiểu at abc (main.js:10) trở lại đúng dòng thực tế trong file UserProfile.tsx của bạn.

Nếu không có source maps, bạn phải đóng vai thám tử với stack trace. Hãy tìm kiếm các mẫu lặp lại trong tên hàm đã bị mã hóa như Xy, Za, hoặc lại là Xy. Sự lặp lại này thường chỉ ra component đang bị lặp. Hãy kiểm tra các commit git gần nhất của bạn—đặc biệt là bất kỳ thay đổi nào liên quan đến việc lấy dữ liệu hoặc logic useEffect phức tạp.

Cách khắc phục triệt để: Phá vỡ vòng lặp

1. Dọn dẹp các Dependency trong useEffect

Hầu hết các lỗi #185 đều bắt nguồn từ các hook. Hãy xem xét sai lầm phổ biến này:

useEffect(() => {
  setCount(count + 1);
}, [count]); // Đây là một vòng lặp vô tận tức thì.

Thay vì theo dõi biến mà bạn đang thay đổi, hãy sử dụng cập nhật dạng hàm. Điều này loại bỏ nhu cầu đưa biến đó vào mảng dependency:

useEffect(() => {
  // Chỉ chạy một lần khi mount
  setCount(prev => prev + 1);
}, []); 

2. Ngừng thực thi các handler khi render

Kiểm tra JSX của bạn để tìm các dấu ngoặc đơn thừa. Nếu bạn thấy <button onClick={doSomething()}>, bạn đã tìm ra thủ phạm. Hàm đó sẽ chạy ngay khi button được render. Nếu doSomething cập nhật state, component sẽ re-render, chạy lại hàm đó và làm ứng dụng bị crash. Luôn luôn truyền tham chiếu: <button onClick={doSomething}>.

3. Chuyển sang Derived State

Đừng sử dụng useEffect để đồng bộ hóa hai phần của state. Việc này vừa kém hiệu quả vừa nguy hiểm. Nếu bạn đang lọc một danh sách dựa trên từ khóa tìm kiếm, đừng lưu trữ filteredList trong state. Thay vào đó, hãy sử dụng useMemo:

// Thay vì kích hoạt lần render thứ hai với useEffect
const filteredData = useMemo(() => {
  return data.filter(item => item.active);
}, [data]);

Cách này tính toán giá trị ngay trong lần render đầu tiên, giúp bạn tiết kiệm cả một bước trong vòng đời và loại bỏ nguy cơ xảy ra vòng lặp.

Các bước xác minh

Đừng chỉ đẩy code lên production và hy vọng mọi thứ ổn thỏa. Hãy xác minh bản sửa lỗi trong môi trường staging trước:

  • Kiểm tra với Profiler: Mở React DevTools và sử dụng tab "Profiler". Ghi lại hoạt động trong vài giây. Nếu bạn thấy một component duy nhất thực hiện commit hàng chục lần mỗi giây, vòng lặp vẫn còn đó.
  • Theo dõi CPU: Theo dõi Task Manager của trình duyệt. Nếu mức sử dụng CPU cho tab của bạn vọt lên 100% và duy trì ở đó khi bạn điều hướng đến một trang cụ thể, logic của bạn vẫn đang bị xoáy vào vòng lặp.

Mẹo chuyên nghiệp để Debug

Đôi khi vòng lặp được kích hoạt bởi dữ liệu lạ trong URL, chẳng hạn như chuỗi Base64 bị lỗi hoặc một tham số truy vấn không mong muốn. Khi tôi bị kẹt lúc 2 giờ sáng để cố gắng tìm hiểu lý do tại sao một state phụ thuộc vào URL gây crash, tôi sử dụng URL Encoder/Decoder trên ToolCraft. Nó giúp tôi nhanh chóng phân tích và kiểm tra các đoạn URL từ production để xem liệu có ký tự ẩn nào khiến logic phân tách của tôi thất bại và kích hoạt vòng lặp cập nhật hay không.

Cuối cùng, hãy để các công cụ hỗ trợ bạn. Đảm bảo rằng eslint-plugin-react-hooks được thiết lập ở mức "error" trong pipeline CI/CD của bạn. Nó sẽ bắt được hầu hết mọi vòng lặp liên quan đến dependency trước khi code rời khỏi máy tính của bạn.

Related Error Notes