Sửa lỗi 'Failed to execute removeChild on Node' trong React

intermediate⚛️ React2026-07-10| React (v16, v17, v18), Chrome, Safari, Edge. Thường bị kích hoạt bởi Google Translate, Grammarly, hoặc trình quản lý mật khẩu.

Error Message

NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
#react#javascript#dom#debugging#web-dev

Vấn đề: Khi DOM bị thay đổi sau lưng ReactỨng dụng React của bạn chạy hoàn hảo trong môi trường phát triển. Nhưng trên production, người dùng báo cáo ứng dụng bị crash hoàn toàn—màn hình trắng đáng sợ. Bạn kiểm tra log và thấy một NotFoundError. Điều này xảy ra vì bản đồ nội bộ của React về trang không còn khớp với HTML thực tế mà trình duyệt đang giữ.

React giả định rằng nó có toàn quyền kiểm soát các DOM node mà nó quản lý. Nếu một tác nhân bên ngoài—như extension trình duyệt hay trình tự động dịch—di chuyển, xóa, hoặc bọc một phần tử, React sẽ mất dấu nó. Khi React cố unmount phần tử đó, trình duyệt ném ra lỗi vì quan hệ cha-con đã bị cắt đứt.

Nguyên nhân phổ biến- Google Translate: Đây là thủ phạm số 1. Nó thường thay thế các text node thô bằng thẻ <font> để áp dụng bản dịch, làm xóa đi node mà React đang theo dõi.- Grammarly và AdBlocker: Các công cụ này thường chèn thêm phần tử <div> hoặc <span> vào giao diện để tô sáng văn bản hoặc hiển thị popup.- Plugin jQuery cũ: Nếu bạn dùng thư viện slider hoặc tooltip cũ tự di chuyển các phần tử, React cuối cùng sẽ cố 'xóa' một phần tử con không còn ở đó nữa.## Tại sao React bị crashReact dùng Virtual DOM để tính toán cách cập nhật giao diện hiệu quả nhất. Nếu bạn thay đổi state để ẩn một component, React thực thi lệnh removeChild trên DOM thực.

Trình duyệt rất nghiêm ngặt. Nếu React gọi parentNode.removeChild(child) nhưng một công cụ dịch đã chuyển child đó vào một container khác, trình duyệt sẽ từ chối yêu cầu. Đây không chỉ là cảnh báo; đây là lỗi nghiêm trọng làm dừng luồng thực thi JavaScript.

Giải pháp 1: Bảo vệ "Text Node"Hầu hết các crash removeChild xảy ra do văn bản động. React thường render chuỗi ký tự thành text node thô. Khi Google Translate bọc văn bản đó trong thẻ <font>, tham chiếu của React đến text node gốc trở thành tham chiếu "chết".

Để khắc phục, đừng bao giờ để chuỗi động trần trụi. Bọc chúng trong một phần tử ổn định như <span>. React dễ theo dõi một phần tử HTML cố định hơn là một đoạn văn bản lơ lửng.

// ❌ Nguy hiểm: Google Translate sẽ làm vỡ đoạn này
{user.isLoggedIn && `Welcome back, ${user.name}`}

// ✅ An toàn: React theo dõi span, không phải text thô
{user.isLoggedIn && <span>Welcome back, {user.name}</span>}

Giải pháp 2: Dùng pattern "Black Box"Nếu bạn bắt buộc phải dùng thư viện như D3.js hoặc widget jQuery cũ có thao tác DOM, bạn cần che khuất nó khỏi React. Tạo một component "Black Box". Đây là container nơi React render phần vỏ ngoài một lần rồi đứng sang một bên, để thư viện bên thứ ba tự làm việc theo cách thủ công.

import { useEffect, useRef } from 'react';

function MapWidget({ coordinates }) {
  const elRef = useRef(null);

  useEffect(() => {
    // Khởi tạo thư viện trên ref
    const map = window.ExternalMapLib.init(elRef.current);
    map.setPosition(coordinates);

    // Quan trọng: Dọn dẹp thủ công để tránh node ma
    return () => map.destroy();
  }, [coordinates]);

  // React chỉ quản lý div này. Nó sẽ không đụng vào bên trong.
  return <div ref={elRef} />;
}

Giải pháp 3: Xử lý React 18 Strict ModeTrong React 18, StrictMode mount component của bạn hai lần trong môi trường phát triển. Điều này nhằm giúp bạn tìm ra lỗi cleanup. Nếu useEffect của bạn tạo một DOM element nhưng hàm cleanup không xóa nó, bạn sẽ có ID trùng lặp hoặc node không khớp. Đây là nguyên nhân phổ biến gây lỗi removeChild khi hot reload.

Luôn kiểm tra lại logic cleanup. Nếu bạn thêm phần tử vào document.body, bạn phải xóa nó khi component unmount.

useEffect(() => {
  const portalNode = document.createElement('div');
  document.body.appendChild(portalNode);

  return () => {
    if (document.body.contains(portalNode)) {
      document.body.removeChild(portalNode);
    }
  };
}, []);

Cách xác nhận đã sửa xongĐừng chỉ giả định lỗi đã biến mất. Bạn có thể mô phỏng các điều kiện gây crash:

  • Bật dịch tự động: Mở ứng dụng trong Chrome. Nhấp chuột phải bất kỳ đâu và chọn "Dịch sang tiếng Việt". Thao tác các phần tử giao diện từng gây crash trước đây.- Kiểm tra phần tử "ma": Dùng tab Elements trong DevTools. Tìm thẻ <font> hoặc các phần tử được extension như Grammarly chèn vào. Nếu chúng nằm trong cây React, hãy bọc các khu vực đó bằng thẻ <span>.- Ghi log trên production: Vì lỗi này khó bắt trong môi trường phát triển cục bộ, hãy dùng công cụ như Sentry hoặc LogRocket để thu thập stack trace từ người dùng thực.

Related Error Notes