How to loop over DOM elements from querySelectorAll
By Flavio Copes
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: