react: "NotFoundError: Failed to execute 'removeChild' on 'Node'" when using React.Fragment <> with Chrome extension which does not modify the DOM tree below the root div of the React app

This has already been discussed before (#14740), but there wasn’t a reproducing example for this kind of issue and I think that my use case is also a bit different.

Do you want to request a feature or report a bug? I believe this can be considered a bug.

What is the current behavior? In order to reproduce this issue using Chrome, you will need to install the following Chrome extension called TransOver:

Screen Shot 2019-11-03 at 22 51 33

https://chrome.google.com/webstore/detail/transover/aggiiclaiamajehmlfpkjmlbadmkledi?hl=en

I use it to translate text on hover. The only thing that this extension does is appending a tooltip with the translated text to the body HTML element when you hover an element with text (it doesn’t seem it appends stuff below the React’s root div element).

I have created two code sandboxes to show you better and explain the problem. It is a minimal example of a movie app like the one Dan showed at JSConf 2018 in Iceland, though not as beautiful as his and without all that cool Suspense stuff, but at least it uses hooks 😃 .

The two code sandboxes are essentially identical, the only difference is that the first one (heuristic-lake-exxvu) uses a div element for MovieApp, whereas the second (magical-grass-016kc) uses a React.Fragment (<></>) component:

heuristic-lake-exxvu’s MovieApp:

const MovieApp = () => {
  const [currentMovie, setCurrentMovie] = useState(initialCurrentMovieState);
  const { isLoading, id: currentMovieId, movieDetails } = currentMovie;
  ...
  return (
    <div> // <======================= Uses a `div`
      {isLoading ? (
        "Loading..."
      ) : (
      ...

magical-grass-016kc’s MovieApp:

const MovieApp = () => {
  const [currentMovie, setCurrentMovie] = useState(initialCurrentMovieState);
  const { isLoading, id: currentMovieId, movieDetails } = currentMovie;
  ...
  return (
    <> // <======================= Uses a fragment
      {isLoading ? (
        "Loading..."
      ) : (
      ...

Now, if you open heuristic-lake-exxvu and click on the Show movie info button of any movie in the list, you will see the Loading... text before the promise with the data of the movie resolves, and the Movie component is rendered.

Before the promise resolves, try hovering on the Loading... text with the TransOver extension enabled, you should see:

Screen Shot 2019-11-03 at 23 26 48

The world makes sense here, no errors, no warnings, everything works.

Now try to do the same thing on magical-grass-016kc, as soon as you hover Loading..., you will see the NotFoundError: Failed to execute 'removeChild' on 'Node' error logged in the browser’s console:

Screen Shot 2019-11-03 at 23 40 00

Screen Shot 2019-11-03 at 23 40 52

Here is a streamable video showing this same error:

https://streamable.com/4gxua

What is the expected behavior? In heuristic-lake-exxvu (uses a div instead of React fragment), everything worked. The TransOver extension appends to body and does not modify the React’s root div neither does it append stuff below it, so I would expect the code in the React fragment example (magical-grass-016kc) to behave the same and work as in heuristic-lake-exxvu.

Chrome is plenty of useful extensions like this one and they should not really interfere with React, I think that users using React applications may also install other extensions which modify the DOM which they find useful. If an extension appends to body like TransOver does, I wouldn’t expect React to have problems with it and cause undesirable effects and application errors like this one.

This is my opinion, I would be very glad to hear what you think about it, and if you think I have spotted a bug of React fragments (I think it’s a bug because, again, it works when using a div in heuristic-lake-exxvu).

Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React?

Browser: Chrome React v16.11.0 React DOM v16.11.0

About this issue

  • Original URL
  • State: open
  • Created 5 years ago
  • Reactions: 26
  • Comments: 28 (1 by maintainers)

Commits related to this issue

Most upvoted comments

It happened in my project by having t() function. The problem was using Google Translate from Chrome. Maybe it is a good solution to set no translate <html translate="no">.

I’m using i18next and in my tests with TransOver, this workaround didn’t work.

My page crashes both using Trans or t().

"react": "^17.0.2",
"react-i18next": "^11.8.15",

Only one solution that worked for me (stop to crash) is inserting this piece of code before my page loads:

if (typeof Node === 'function' && Node.prototype) {
  const originalRemoveChild = Node.prototype.removeChild;
  Node.prototype.removeChild = function (child) {
    if (child.parentNode !== this) {
      if (console) {
        console.warn('Cannot remove a child from a different parent', child, this);
      }
      return child;
    }
    return originalRemoveChild.apply(this, arguments);
  };

  const originalInsertBefore = Node.prototype.insertBefore;
  Node.prototype.insertBefore = function (newNode, referenceNode) {
    if (referenceNode && referenceNode.parentNode !== this) {
      if (console) {
        console.warn(
          'Cannot insert before a reference node from a different parent',
          referenceNode,
          this
        );
      }
      return newNode;
    }
    return originalInsertBefore.apply(this, arguments);
  };
}

Since most references are referring to i18n libraries, I suspect this may be from Chrome’s auto-translation feature instead. image I can reproduce consistently for both positinve and negative

Having the same issue here. Some translation extension is messing up fragment components

I have added the following patched Node.prototype.removeChild method to a fork of magical-grass-016kc -> https://codesandbox.io/s/tender-lehmann-ryygj:

...
if (typeof Node === "function" && Node.prototype) {
  const originalRemoveChild = Node.prototype.removeChild;
  Node.prototype.removeChild = function(child) {
    console.log("removeChild...");
    console.log("this", this);
    console.log("this.outerHTML", this.outerHTML);
    console.log("child", child);
    console.log(
      "this.childNodes",
      this.childNodes.length,
      Array.prototype.slice
        .call(this.childNodes)
        .map(child => console.warn("child.nodeValue", child.nodeValue))
    );
    console.log("child.parentNode", child.parentNode.outerHTML);
    // debugger;
    if (child.parentNode !== this) {
      if (console) {
        console.error(
          "Cannot remove a child from a different parent",
          child,
          this
        );
      }
      return child;
    }
    return originalRemoveChild.apply(this, arguments);
  };
}
...

At the time when React throws the error (hover on the Loading... text), Loading... is the only child of <div id="root"></div>, but the strange thing I noticed is that the Loading...’ text node’s parentNode is null (child.parentNode). Also I console.warned the childNodes of this (the expected parent) and you can see that the only node is the Loading... text node:

Screen Shot 2019-11-04 at 08 44 44

Could it be that TransOver replaces the original “Loading…” text node with a different one and React does not know that? But then, if TransOver does this, I would expect to see child.parentNode of this new text node to be again <div id="root"></div>, but it’s null at the time of the error…

What could you advise me to do in order to replicate this issue without using TransOver? I do not know what kind of DOM mutation it performs…

这问题太明显,在代码中只要使用<></>,在发生热更新的时候就会触发这个问题,这意味着,使用<></>(React.Fragment)就没办法使用react18。 补充:只有在使用ReactDom.createRoot的情况下才会触发问题,如果直接使用ReactDom.render就没问题 image

if you want duplicate bug, you can use this project: https://gitee.com/shuzipai/meikeyun-create-app git pull and yarn dev, change code, you will be find this bug

Hey everyone, I also got this error when using google translate extension on Chrome. Didnt find a workaround yet. Will try to replace all <></> for <div></div>.

@aweary I was able to reproduce this in this codesandbox: https://vt25p.csb.app/