Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"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 #17256

Open
tonix-tuft opened this issue Nov 3, 2019 · 33 comments

Comments

@tonix-tuft
Copy link

tonix-tuft commented Nov 3, 2019

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

@aweary
Copy link
Contributor

aweary commented Nov 4, 2019

@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?

@tonix-tuft
Copy link
Author

tonix-tuft commented Nov 4, 2019

@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
Copy link
Author

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

@kasperpeulen
Copy link

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

@tonix-tuft
Copy link
Author

I can reproduce your error too.

@kasperpeulen
Copy link

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

@tonix-tuft
Copy link
Author

Don't you think they are related?

@Valenoern
Copy link

Valenoern commented May 20, 2020

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
mastodon/mastodon#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)

@bklatka
Copy link

bklatka commented Sep 16, 2020

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

@FabianSellmann
Copy link

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

@firasdib
Copy link

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?

@codingyourlife
Copy link

codingyourlife commented Feb 14, 2021

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.

@pradella
Copy link

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

.

@ahmad-ali14
Copy link

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

@codingyourlife
Copy link

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

@thiagomorales
Copy link

thiagomorales commented Apr 29, 2021

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

@Bram-Zijp
Copy link

Related to #11538

@clehene
Copy link

clehene commented Dec 10, 2021

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

@joaquimpvh
Copy link

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.

@Mahdiyeh
Copy link

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

@shuzipai
Copy link

shuzipai commented Jul 15, 2022

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

@artemave
Copy link

TransOver has been fixed to have <html translate="no"> in its popup html (artemave/translate_onhover#94)

@Bram-Zijp
Copy link

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: #11538 (comment)
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';

@ProfBramble
Copy link

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

@yuki2006
Copy link

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.

ahmadxgani added a commit to ahmadxgani/next-blog-frontend that referenced this issue Mar 11, 2023
@manharsinh22
Copy link

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

@tmhuyy
Copy link

tmhuyy commented Oct 15, 2023

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 ?

@Josema
Copy link

Josema commented Jan 22, 2024

I had this same error using react-native-web, and using Google Translate Page. The specific component that was giving me problems was this one.

function Dots() {
    const [dots, setDots] = useState('...')
    useEffect(() => {
        const interval = setTimeout(() => {
            setDots(dots.length === 3 ? '' : dots + '.')
        }, 500)
        return () => {
            clearInterval(interval)
        }
    }, [dots])
    return dots
}

The way I fixed is by returning a wrapper component instead of pure string.

    ...
    return <DotsContainer>{dots}</DotsContainer>
}

const DotsContainer = styled.Text``

@zhangdongming0607
Copy link

thiagomorales
thank you, sir. it helps a lot~

@milyes
Copy link

milyes commented Aug 27, 2024

Cela a déjà été discuté auparavant ( #14740 ), mais il n'y avait pas d'exemple de reproduction pour ce type de problème et je pense que mon cas d'utilisation est également un peu différent.

Vous souhaitez demander une fonctionnalité ou signaler un bug ? Je pense que cela peut être considéré comme un bug.

Quel est le comportement actuel ? Pour reproduire ce problème avec Chrome, vous devrez installer l'extension Chrome suivante appelée TransOver :

Capture d'écran 2019-11-03 à 22 h 51 min 33 s

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

Je l'utilise pour traduire du texte au survol. La seule chose que fait cette extension est d'ajouter une info-bulle avec le texte traduit à l' bodyélément HTML lorsque vous survolez un élément avec du texte (il ne semble pas qu'elle ajoute des éléments sous l'élément racine de React div).

J'ai créé deux sandbox de code pour mieux vous montrer et expliquer le problème. C'est un exemple minimal d'une application de film comme celle que Dan a montrée à la JSConf 2018 en Islande, bien que pas aussi belle que la sienne et sans tout ce truc de suspense cool, mais au moins elle utilise des hooks :) .

Les deux sandbox de code sont essentiellement identiques, la seule différence est que le premier ( heuristic-lake-exxvu) utilise un divélément pour MovieApp, tandis que le second ( magical-grass-016kc) utilise un composant React.Fragment( ) :<></>

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

Maintenant, si vous ouvrez heuristic-lake-exxvuet cliquez sur le Show movie infobouton de n'importe quel film dans la liste, vous verrez le Loading...texte avant que la promesse avec les données du film se résolve et le Moviecomposant est rendu.

Avant que la promesse ne soit résolue, essayez de survoler le Loading...texte avec l' TransOverextension activée, vous devriez voir :

Capture d'écran 2019-11-03 à 23 h 26 min 48 s

Ici, le monde a du sens, pas d'erreurs, pas d'avertissements, tout fonctionne.

Essayez maintenant de faire la même chose sur magical-grass-016kc, dès que vous survolez Loading..., vous verrez l' NotFoundError: Failed to execute 'removeChild' on 'Node'erreur enregistrée dans la console du navigateur :

Capture d'écran 2019-11-03 à 23h40

Capture d'écran 2019-11-03 à 23h40 52

Voici une vidéo en streaming montrant cette même erreur :

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

@milyes
Copy link

milyes commented Aug 27, 2024

ok

@milyes
Copy link

milyes commented Sep 1, 2024

Cela a déjà été discuté auparavant ( #14740 ), mais il n'y avait pas d'exemple de reproduction pour ce type de problème et je pense que mon cas d'utilisation est également un peu différent.

Vous souhaitez demander une fonctionnalité ou signaler un bug ? Je pense que cela peut être considéré comme un bug.

Quel est le comportement actuel ? Pour reproduire ce problème avec Chrome, vous devrez installer l'extension Chrome suivante appelée TransOver :

Capture d'écran 2019-11-03 à 22 h 51 min 33 s

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

Je l'utilise pour traduire du texte au survol. La seule chose que fait cette extension est d'ajouter une info-bulle avec le texte traduit à l' bodyélément HTML lorsque vous survolez un élément avec du texte (il ne semble pas qu'elle ajoute des éléments sous l'élément racine de React div).

J'ai créé deux sandbox de code pour mieux vous montrer et expliquer le problème. C'est un exemple minimal d'une application de film comme celle que Dan a montrée à la JSConf 2018 en Islande, bien que pas aussi belle que la sienne et sans tout ce truc de suspense cool, mais au moins elle utilise des hooks :) .

Les deux sandbox de code sont essentiellement identiques, la seule différence est que le premier ( heuristic-lake-exxvu) utilise un divélément pour MovieApp, tandis que le second ( magical-grass-016kc) utilise un composant React.Fragment( ) :<></>

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

Maintenant, si vous ouvrez heuristic-lake-exxvuet cliquez sur le Show movie infobouton de n'importe quel film dans la liste, vous verrez le Loading...texte avant que la promesse avec les données du film se résolve et le Moviecomposant est rendu.

Avant que la promesse ne soit résolue, essayez de survoler le Loading...texte avec l' TransOverextension activée, vous devriez voir :

Capture d'écran 2019-11-03 à 23 h 26 min 48 s

Ici, le monde a du sens, pas d'erreurs, pas d'avertissements, tout fonctionne.

Essayez maintenant de faire la même chose sur magical-grass-016kc, dès que vous survolez Loading..., vous verrez l' NotFoundError: Failed to execute 'removeChild' on 'Node'erreur enregistrée dans la console du navigateur :

Capture d'écran 2019-11-03 à 23h40

Capture d'écran 2019-11-03 à 23h40 52

Voici une vidéo en streaming montrant cette même erreur :

https://streamable.com/4gxua

Quel est le comportement attendu ? Dans heuristic-lake-exxvu(utilise un divfragment à la place de React), tout a fonctionné. L'extension TransOver ajoute bodyet ne modifie pas la racine de React divet n'ajoute pas d'éléments en dessous, donc je m'attendrais à ce que le code dans l'exemple de fragment React ( magical-grass-016kc) se comporte de la même manière et fonctionne comme dans heuristic-lake-exxvu.

Chrome regorge d'extensions utiles comme celle-ci et elles ne devraient pas vraiment interférer avec React. Je pense que les utilisateurs utilisant des applications React peuvent également installer d'autres extensions qui modifient le DOM qu'ils trouvent utiles. Si une extension s'ajoute au corps comme le fait TransOver, je ne m'attendrais pas à ce que React ait des problèmes avec elle et provoque des effets indésirables et des erreurs d'application comme celle-ci.

C'est mon avis, je serais très heureux d'entendre ce que vous en pensez, et si vous pensez que j'ai repéré un bug des fragments React (je pense que c'est un bug car, encore une fois, cela fonctionne lors de l'utilisation d'un divin heuristic-lake-exxvu).

Quelles versions de React et quels navigateurs/systèmes d'exploitation sont concernés par ce problème ? Est-ce que cela fonctionnait dans les versions précédentes de React ?

Navigateur : Chrome React v16.11.0 React DOM v16.11.0

const MovieApp = () => {
const [currentMovie, setCurrentMovie] = useState(initialCurrentMovieState);
const { isLoading, id: currentMovieId, movieDetails } = currentMovie;
...
return (

// <======================= Uses a div
{isLoading ? (
"Loading..."
) : (
...

@0xshawn
Copy link

0xshawn commented Sep 11, 2024

same here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests