Search code examples
javascriptjqueryclassdomruntime-error

Trying to traverse the DOM, but getting Uncaught TypeError parentNode is undefined


I'm trying to move away from jQuery into plain javascript, but am having difficulty traversing the DOM as I did with jQuery. I'm getting an "Uncaught TypeError: html.parentNode is undefined" error, after writing to the DOM, when trying to move back up the DOM structure. Here's the code:

Javascript

let html = 'change to this';

let eA = document.querySelector('.startA');

eA.parentNode
    .querySelector('.target1A .target2A')
    .innerHTML = html
    .parentNode
    .querySelector('.target1A')
    .classList
    .remove('hidden');

HTML

<div id="top">
    <div class="startA">
        <div class="target1A hidden">
            <div class="target2A">target 1</div>
        </div>
    </div>
</div>

I can't seem to figure out why this is happening. I think I'm not completely understanding how to move through the DOM yet. Any help is greatly appreciated.

Thanks


Solution

  • The assignment operator has low precedence. Your code is equivalent to:

    const result = html
        .parentNode
        .querySelector('.target1A')
        .classList
        .remove('hidden');
    
    eA.parentNode
        .querySelector('.target1A .target2A')
        .innerHTML = result;
    

    Which doesn't work - you can't chain the assignment operator like you're trying to do. Select the element after inserting the HTML, as a separate statement.

    You also don't need to select target1A, since it's already the parent of the target2A.

    const target = eA.parentNode.querySelector('.target1A .target2A');
    target.innerHTML = html;
    target.parentNode
        .classList
        .remove('hidden');
    

    const eA = document.querySelector('.startA');
    const target = eA.parentNode.querySelector('.target1A .target2A');
    target.innerHTML = 'foobar';
    target.parentNode
        .classList
        .remove('hidden');
    <div id="top">
        <div class="startA">
            <div class="target1A hidden">
                <div class="target2A">target 1</div>
            </div>
        </div>
    </div>

    If you wanted to do this in a chainable fashion, and since your question is tagged with jQuery, you can use that:

    $('.startA')
      .find('.target1A .target2A')
      .html(html)
      .parent()
      .removeClass('hidden');
    

    $('.startA')
      .find('.target1A .target2A')
      .html('foobar')
      .parent()
      .removeClass('hidden');
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div id="top">
        <div class="startA">
            <div class="target1A hidden">
                <div class="target2A">target 1</div>
            </div>
        </div>
    </div>