Search code examples
javascripthtmlloopshtmlcollection

Wrapping elements while looping through HTMLCollection causes problem


I want to wrap each item of the container in a div. When I loop through HTMLCollection, some elements are accessed multiple times while others are left out

HTML

<div class="container">
    <div class="item_1"></div>
    <div class="item_2"></div>
    <div class="item_3"></div>
    <div class="item_4"></div>
    <div class="item_5"></div>
    <div class="item_6"></div>
    <div class="item_7"></div>
    <div class="item_8"></div>
    <div class="item_9"></div>
</div>

JS

const container = document.querySelector('.container');
const items = container.children;

for(let i = 0; i < items.length; i++) {
    const wrapper = document.createElement('div');
    wrapper.classList.add('wrapper');
    wrapper.appendChild(items[i]);
    container.appendChild(wrapper);
}

Looping directly through HTMLCollection gives this bizarre result

<div class="container">
        <div class="item_2"></div>
        <div class="item_4"></div>
        <div class="item_6"></div>
        <div class="item_8"></div>
        <div class="wrapper">
            <div class="item_1"></div>
        </div>
        <div class="wrapper">
            <div class="item_5"></div>
        </div>
        <div class="wrapper">
            <div class="item_9"></div>
        </div>
        <div class="wrapper">
            <div class="wrapper">
                <div class="item_7"></div>
            </div>
        </div>
        <div class="wrapper">
            <div class="wrapper">
                <div class="wrapper">
                    <div class="wrapper">
                        <div class="item_3"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>

problem gets solved when I convert HTMLCollection to an Array

const items = Array.from(container.children);

I can't understand what causes such behavior


Solution

  • You were iterating the container.children list which you were also changing during the iterations. This messed up the iteration. You can solve this, as you mentioned yourself, by converting the container.children to an array because then you are not iterating over the live container.children list but over an array copy of that. This copy is still referring to the correct child elements so they are moved correctly with the appendChild() function.

    As an alternative you can use the querySelecterAll() to retrieve all the elements you want to wrap.

    const container = document.querySelector('.container');
    const items = container.querySelectorAll('.container > *');
    
    for(let i = 0; i < items.length; i++) {
        const wrapper = document.createElement('div');
        wrapper.classList.add('wrapper');
        wrapper.appendChild(items[i]);
        container.appendChild(wrapper);
    }
    .wrapper {
      background-color: red;
    }
    <div class="container">
        <div class="item_1">1</div>
        <div class="item_2">2</div>
        <div class="item_3">3</div>
        <div class="item_4">4</div>
        <div class="item_5">5</div>
        <div class="item_6">6</div>
        <div class="item_7">7</div>
        <div class="item_8">8</div>
        <div class="item_9">9</div>
    </div>