skip to Main Content

Here is the code I have right now. It’s purpose is to place an event listener on every button with the same class using querySelectorAll and some loop (currently using forEach), and make it a togglable "fav", toggling which will add or subtract 1 from the text in the span inside the button:

const likeButtonArray = document.querySelectorAll('.likeButton');
const likeCountArray = document.querySelectorAll('.likeCount');
let likeButton = Array.from(likeButtonArray);
let likeCount = Array.from(likeCountArray);
// let count;
// [...likeButton].forEach((node) => console.log(node));
likeButton.forEach((like,index) => {
  /* Uncaught TypeError: Cannot read properties of null
  at NodeList.forEach (<anonymous>), at scr.js:3:12 */
  let count = parseInt(document.querySelector(`#likeCount:nth-of-type(${index+1})`).innerHTML);
  // let count = parseInt(document.querySelector(`like.firstElementChild`).innerHTML);
  // Uncaught TypeError: Cannot read properties of null (reading 'innerHTML' at scr.js:4:84
  console.log(`assigned: ${index+1} ~ ${count}`)
  let counter = false;
  like.addEventListener('click', () => {
    counter = !counter;
    if(counter) {
      console.log(`added for   ${index}`);
      count++;
      // like.classList.add('active');
      likeCount[index].innerHTML = toString(parseInt(like.innerHTML) + 1);
    } else {
      console.log(`removed for ${index}`);
      count--;
      // like.classList.remove('active');
      likeCount[index].innerHTML = toString(parseInt(like.innerHTML) - 1);
    }
    like.classList.toggle('active');
    console.log(`clicked:    ${index+1} ~ ${count}`)
    // likeCount[index].textContent = count.toString();
  });
});
* {
  border: 0;
  outline: 0;
  box-sizing: border-box;
}
.likeButton {
  background: black;
  color: white;
}
.likeButton.active {
  border: 1px dashed blue;
  background: white;
  color: black;
}
<div class="wrap">
  <button class="likeButton">
  <span class="likeCount">69</span></button>
</div>
<div class="wrap">
  <button class="likeButton">
  <span class="likeCount">420</span></button>
</div>

I had the span outside of the button earlier, but it has to be inside of it, so some JS is still leftover from the before. But, the code errors stayed the same:

scr.js:5 Uncaught TypeError: Cannot read properties of null (reading 'innerHTML')
    at scr.js:5:84
    at NodeList.forEach (<anonymous>)
    at scr.js:3:12

The code is not working, at all. Here is what is happening:

The first button is the only one which has an event listener, all others do nothing when clicked.

It’s inner HTML is changed to "[objectUndefined]", and the class gets toggled.

I also get all the console logs that I placed.

EDIT: I decided to convert nodeLists to arrays by adding lines 3 and 4, and now nothing is working.

Why? How can I fix this, while keeping my code D.R.Y.?

2

Answers


  1. Simply use a regular old for loop as document.querySelectorAll() returns a node-list and forEach may not be supported on all browsers for node-list. Since likeCount will have the same index as likeButton , I have replaced the code for getting count by let count = parseInt(likeCount[index].innerHTML);. You also, don’t need to use toString as innerHTML can take number and will parse automatically. the code is cleaner and more readable now.

    const likeButton = document.querySelectorAll('.likeButton');
    const likeCount = document.querySelectorAll('#likeCount');
    for (let index = 0; index < likeButton.length; index++) {
      const like = likeButton[index];
      let count = parseInt(likeCount[index].innerHTML);
      console.log(`assigned: ${index+1} ~ ${count}`)
      let counter = false;
      like.addEventListener('click', () => {
        counter = !counter;
        if (counter) {
          count++;
          likeCount[index].innerHTML =(parseInt(likeCount[index].innerHTML) + 1);
        } else {
          count--;
          likeCount[index].innerHTML = parseInt(likeCount[index].innerHTML) - 1;
        }
        like.classList.toggle('active');
        
      });
    }
    * {
      border: 0;
      outline: 0;
      box-sizing: border-box;
    }
    
    .likeButton {
      background: black;
      color: white;
    }
    
    .likeButton.active {
      border: 1px dashed blue;
      background: white;
      color: black;
    }
    <div class="wrap">
      <button class="likeButton">
      <span id="likeCount">69</span></button>
    </div>
    <div class="wrap">
      <button class="likeButton">
      <span id="likeCount">420</span></button>
    </div>
    Login or Signup to reply.
  2. You are really over complicating things. Select the buttons. Add a click listener to the button. Toggle the class. Select the element in the button that has the count and update the count based on the selection.

    const likeButtonArray = document.querySelectorAll('.likeButton');
    
    likeButtonArray.forEach(button => {
    
      // add the click event to the button
      button.addEventListener("click", e => {
    
        // toggle the class
        button.classList.toggle("active");
    
        // Determine if active or not to do the calculation
        const dir = button.classList.contains("active") ? 1 : -1;
    
        // find the count holder in the button
        const countElem = button.querySelector(".likeCount");
    
        // update the count
        const likes = +countElem.textContent + dir;
        countElem.textContent = likes;
      });
    
    });
    * {
      border: 0;
      outline: 0;
      box-sizing: border-box;
    }
    .likeButton {
      background: black;
      color: white;
    }
    .likeButton.active {
      border: 1px dashed blue;
      background: white;
      color: black;
    }
    <div class="wrap">
      <button class="likeButton">
      <span class="likeCount">69</span></button>
    </div>
    <div class="wrap">
      <button class="likeButton">
      <span class="likeCount">420</span></button>
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search