Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Failed to execute 'removeChild' on 'Node'

Tags:

javascript

I'm using http://alexgorbatchev.com/SyntaxHighlighter/ to highlight code on my website but sometimes in my log im getting Javascript errors like this :

Uncaught NotFoundError: Failed to execute 'removeChild' on 'Node': The node to be removed is no longer a child of this node. Perhaps it was moved in a 'blur' event handler?

Uncaught NotFoundError: An attempt was made to reference a Node in a context where it does not exist.

// set up handler for lost focus
attachEvent(textarea, 'blur', function(e)
{
   textarea.parentNode.removeChild(textarea);
   removeClass(highlighterDiv, 'source');
});

Here is the attachEvent() function code :

function attachEvent(obj, type, func, scope)
{
    function handler(e)
    {
        e = e || window.event;
        
        if (!e.target)
        {
            e.target = e.srcElement;
            e.preventDefault = function()
            {
                this.returnValue = false;
            };
        }
            
        func.call(scope || window, e);
    };
    
    if (obj.attachEvent) 
    {
        obj.attachEvent('on' + type, handler);
    }
    else 
    {
        obj.addEventListener(type, handler, false);
    }
};

Can anyone help getting this fixed ?

like image 837
scootergrisen Avatar asked Feb 21 '14 05:02

scootergrisen


3 Answers

I had to deal with the same issue, and the answer is confusing and counter intuitive. Well, it has its logic, but leads to non-trivial behaviours.

Essentially, when a node is removed from the DOM tree, it fires a blur event (and before a focusout event too).

So, when you call removeChild, the blur event is fired again, but this time textarea still has its parentNode defined, but textarea isn't among its parent's children anymore! (Yes, read this twice. Or more.)

This happens in Chrome for now, although Firefox has planned to do the same for quite some time.

As a workaround, you can remove the event listener you attached:

var onblur = function(e) {
    detachEvent(textarea, 'blur', onblur);
    textarea.parentNode.removeChild(textarea);
    removeClass(highlighterDiv, 'source');
};
attachEvent(textarea, 'blur', onblur);

I'm assuming that you have some detachEvent function to remove event listeners. Adjust the code to your needs.

Alternatively, you can set a flag (like a property, or an attribute, or better a scoped variable) on the textarea in the listener function, and check for it before proceeding with the node removal:

var removed = false;
attachEvent(textarea, 'blur', function(e) {
    if (removed) return;
    removed = true;
    textarea.parentNode.removeChild(textarea);
    removeClass(highlighterDiv, 'source');
});

You can also check if textarea if actually a child node of its parentNode before removing it, but such test is so counter-intuitive (at least to me) that I wouldn't recommend doing that, in fear that this behaviour will be changed in the future.

Finally, you can always rely on a try...catch statement, but... ugh.

2016 Update

Naturally, using a framework like jQuery would save you a lot of work attaching event listeners with one, but this functionality will come to standard addEventListener too:

textarea.addEventListener('blur', handler, { once: true });
like image 146
MaxArt Avatar answered Nov 07 '22 09:11

MaxArt


While the classic answer for this question was that it's a bug in the JS code, with React 16 there is a major bug which means that any browser/extension mechanism which modifies the DOM breaks React.

Google Chrome's built in Translate functionality is the most common culprit.

GitHub issue: https://github.com/facebook/react/issues/11538

Minimal case: https://p93xxmr0rq.codesandbox.io/ (just manually right click "Translate to" in Chrome and select from Japanese then click the button)

Workaround: How to disable google translate from html in chrome

<meta name="google" content="notranslate">
like image 18
hyperknot Avatar answered Nov 07 '22 09:11

hyperknot


Try to translate your site with Google Chrome (right click on the site and choose "Translate to English". If you see the error happening in the console, then you know it's caused by Google Translate.

Related GitHub issue: https://github.com/facebook/react/issues/11538.


Workaround from Dan Abramov:

if (typeof Node === 'function' && Node.prototype) {
  const originalRemoveChild = Node.prototype.removeChild;
  Node.prototype.removeChild = function(child) {
    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);
  }

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

Run this code before your app is rendered. Please keep in mind, that this has a slight performance hit.


Workaround from Shuhei Kagawa

Render texts in <span>.

// Case 1
<div>
  {condition && 'Welcome'}
  <span>Something</span>
</div>

// A workaround for case 1
<div>
  {condition && <span>Welcome</span>}
  <span>Something</span>
</div>

// Case 2
<div>
  {condition && <span>Something</span>}
  Welcome
</div>

// A workaround for case 2
<div>
  {condition && <span>Something</span>}
  <span>Welcome</span>
</div>

A detailed explanation can be found here: https://github.com/facebook/react/issues/11538#issuecomment-390386520

like image 8
Dávid Molnár Avatar answered Nov 07 '22 11:11

Dávid Molnár