react icon indicating copy to clipboard operation
react copied to clipboard

"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

Open tonix-tuft opened this issue 4 years ago • 24 comments

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 :) .

  • https://codesandbox.io/s/heuristic-lake-exxvu

  • https://codesandbox.io/s/magical-grass-016kc

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

tonix-tuft avatar Nov 03 '19 23:11 tonix-tuft

@tonix-tuft is there any way you can reproduce the issue without the TransOver extension? It seems likely that the extension is doing something other than just appending an element to the body. Maybe try using Chrome's debugger to see what element it is failing on?

aweary avatar Nov 04 '19 02:11 aweary

@aweary I could try patching Node.removeChild() and log the problematic child element.

I think this issue is related to:

https://stackoverflow.com/questions/21926083/failed-to-execute-removechild-on-node

tonix-tuft avatar Nov 04 '19 06:11 tonix-tuft

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...

tonix-tuft avatar Nov 04 '19 07:11 tonix-tuft

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

kasperpeulen avatar Jan 17 '20 16:01 kasperpeulen

I can reproduce your error too.

tonix-tuft avatar Jan 19 '20 20:01 tonix-tuft

Ah, yeah this is actually a different issue. Transover does not mutate the dom like google translate does.

kasperpeulen avatar Jan 21 '20 07:01 kasperpeulen

Don't you think they are related?

tonix-tuft avatar Jan 21 '20 14:01 tonix-tuft

While I'm not sure, this might be the same problem as happens with Chromoji and scrolling mastodon feeds.

https://github.com/smeeckaert/chromoji/issues/15
https://github.com/tootsuite/mastodon/issues/13814

react-dom.production.min.js:5058 DOMException:
Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

Chromium: 80.0.3987.163 (Official Build) (64-bit)

Valenoern avatar May 20 '20 06:05 Valenoern

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

bklatka avatar Sep 16 '20 11:09 bklatka

Is the workarround here then not to put text directly in a fragment, but rather wrap it with a proper dom element?

FabianSellmann avatar Nov 20 '20 04:11 FabianSellmann

I've been seeing these errors on regex101.com quite a lot, around 20,000 times in the past 14 days. After some googling I found this, and it seems like this might be the root cause. What are the mitigations? Is there any viable workaround?

firasdib avatar Jan 29 '21 10:01 firasdib

I can reproduce this and on our page it lead to registration crashing with a blank page when clicking a checkbox (not just locally also in production). I could solve it by changing the translated text from i18next <Trans /> component to the t() function. error fix

I will report this to react i18next too and hope for a quickfix. I will point to this open issue. All in all it belongs here and I can confirm that it is still an issue.

codingyourlife avatar Feb 14 '21 20:02 codingyourlife

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

.

pradella avatar Feb 18 '21 23:02 pradella

I removed the chrome translation extension, didn't work.

ahmad-ali14 avatar Feb 23 '21 12:02 ahmad-ali14

For i18next with TransOver there is now a workaround available here. I still hope that this is getting fixed legitimately...

codingyourlife avatar Feb 24 '21 00:02 codingyourlife

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);
  };
}

thiagomorales avatar Apr 29 '21 23:04 thiagomorales

Related to https://github.com/facebook/react/issues/11538

Bram-Zijp avatar May 01 '21 11:05 Bram-Zijp

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

clehene avatar Dec 10 '21 19:12 clehene

Estou usando o i18next e em meus testes com TransOver, essa solução alternativa não funcionou.

Minha página trava usando Transou t().

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

Apenas uma solução que funcionou para mim (parar para travar) é inserir este trecho de código antes que minha página carregue:

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);
  };
}

Para mim funcionou, e eu estou utilizando o EDGE que estava com este bug.

joaquimpvh avatar Jan 07 '22 10:01 joaquimpvh

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">.

Mahdiyeh avatar Jan 26 '22 10:01 Mahdiyeh

这问题太明显,在代码中只要使用<></>,在发生热更新的时候就会触发这个问题,这意味着,使用<></>(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

shuzipai avatar Jul 15 '22 03:07 shuzipai

TransOver has been fixed to have <html translate="no"> in its popup html (https://github.com/artemave/translate_onhover/pull/94)

artemave avatar Aug 23 '22 14:08 artemave

If you want to allow your users to still translate the page (which is recommended if you have global visitors). I recommend using the workaround in the following comment: https://github.com/facebook/react/issues/11538#issuecomment-390386520 If I'm not mistaken, this error also occurs in the following example;

const SomeComponent = ({ hasStyles }) =>
  hasStyles ? <span style={...}>hello</span> : 'no styles and unwrapped';

Bram-Zijp avatar Aug 24 '22 14:08 Bram-Zijp

Now there are several ways to bypass this problem, but I would like to ask if there is an estimated repair time for react itself @aweary

ProfBramble avatar Oct 14 '22 04:10 ProfBramble

It seemed like a possible problem with extensions that manipulate the DOM, not just translation extensions.

Essentially, I thought the rendering engine manipulated the DOM that the application developer expected and the extension manipulated the DOM cloned from it and presented it to the end user.

yuki2006 avatar Jan 22 '23 13:01 yuki2006

Hi Team, Not sure is this issue resolved or is it still?

manharsinh22 avatar Oct 13 '23 10:10 manharsinh22

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

.

@pradella, Did you fix this error ?

tmhuyy avatar Oct 15 '23 03:10 tmhuyy