How to loop over DOM elements from querySelectorAll

By

Learn how to loop over the elements returned by querySelectorAll, which gives you a NodeList rather than an array, using a clean JavaScript for..of loop.

~~~

The easiest way to loop over the elements returned by querySelectorAll() is the for..of loop:

for (const button of document.querySelectorAll('.buttons')) {
  button.addEventListener('click', () => {
    console.log('clicked', button.textContent)
  })
}

The querySelectorAll() method run on document returns a list of DOM elements that satisfy the selectors query.

It returns a list of elements, which is not an array but a NodeList object. A NodeList is iterable, so for..of works on it directly.

Using forEach

A NodeList also has a forEach() method:

document.querySelectorAll('.buttons').forEach((button, index) => {
  console.log(index, button.textContent)
})

The second parameter gives you the index of each element, which a plain for..of loop does not.

If you want the index while keeping for..of, use entries():

for (const [index, button] of document.querySelectorAll('.buttons').entries()) {
  console.log(index, button.textContent)
}

A classic for loop works too, since a NodeList has a length property and supports access by index:

const buttons = document.querySelectorAll('.buttons')

for (let i = 0; i < buttons.length; i++) {
  console.log(buttons[i].textContent)
}

There’s rarely a reason to prefer it today, but you’ll still see it in older code.

The pitfall: it looks like an array, but it’s not

A NodeList is missing most array methods. Calling map() on it throws:

document.querySelectorAll('.buttons').map((b) => b.textContent)
//TypeError: document.querySelectorAll(...).map is not a function

The fix is to convert it to a real array first, with Array.from():

const labels = Array.from(document.querySelectorAll('.buttons')).map(
  (b) => b.textContent
)

Or with the spread operator:

const buttons = [...document.querySelectorAll('.buttons')]

Once converted, you can use map(), filter(), and every other array method.

Static, not live

One more thing worth knowing. The NodeList returned by querySelectorAll() is static: a snapshot of the DOM at the moment you called it. If a matching element is added to the page later, the list does not update. You need to call querySelectorAll() again to see it.

This is different from document.getElementsByClassName(), which returns a live HTMLCollection that updates as the DOM changes. A live collection can surprise you if you add or remove elements while looping over it. The static list from querySelectorAll() is easier to reason about, and it’s the one I reach for.

~~~

Related posts about platform: