DEV Community

Sanket N Jain
Sanket N Jain

Posted on

cannot read property 'classlist' of undefined javascript

Alt Text

I'm practicing a challenge of making an order checkout page. And I have made a few payment options using the radio button. The text content inside will be visible only if I click on it. But when I click on the radio button it doesn't show the content inside it and shows the "Cannot read property 'classList' of undefined" error.

Here is the Javascript code:

var Input = document.querySelectorAll("input[name='method']");
var show = document.querySelectorAll(".on");

for(var i = 0, max = Input.length; i < max; i++) {
Input[i].onclick = function() {
show[i].classList.add("visible");
show[i].classList.remove("hide");
}

}

Here is a link to my codepen for HTML and CSS code:

https://codepen.io/sanket-n-jain/pen/jOWypYM

Top comments (3)

Collapse
 
daniel13rady profile image
Daniel Brady • Edited

Hi @sanketnjain5 !

You have discovered a hard-to-find bug! πŸ˜…

I will tell you my recommended solutions now, but please read on to understand why they work:

Option 1: Use let instead of var

for(let i = 0, max = Input.length; i < max; i++) {
  Input[i].onclick = function() {
    show[i].classList.add("visible");
    show[i].classList.remove("hide");
  }
}

Option 2: Copy i into a new variable inside your loop to save its current value

for(var i = 0, max = Input.length; i < max; i++) {
  var x = i;
  Input[x].onclick = function() {
    show[x].classList.add("visible");
    show[x].classList.remove("hide");
  }
}

I believe the root of your problem lies in a misunderstanding of how variable scope and variables declared with var work. Let me try to illustrate with a simpler example. πŸ™

var i = 3;
function foo() {
  return i;
}

foo(); // => 3
i = 42;
foo(); // => 42

When you create a function that references a variable defined outside the function, it creates what we call a "closure". You can imagine the variable is a box, and the function is closing its little hands around that box.

But it is important to remember: the function is not holding onto what is inside the box, it is simply holding onto the box. If someone reaches into the box and replaces the contents, your function is still holding the box, but the next time it reaches inside it, it'll pull out something different.

Now, going back to what is happening in your code.

You can imagine that the loop you have written:

for(var i = 0, max = Input.length; i < max; i++) {
  Input[i].onclick = function() {
    show[i].classList.add("visible");
    show[i].classList.remove("hide");
  }
}

Can be rewritten like this:

var i = 0;
Input[i].onclick = function() {
  show[i].classList.add("visible");
  show[i].classList.remove("hide");
};
i = i + 1;

Input[i].onclick = function() {
  show[i].classList.add("visible");
  show[i].classList.remove("hide");
};
i = i + 1;

Input[i].onclick = function() {
  show[i].classList.add("visible");
  show[i].classList.remove("hide");
};
i = i + 1;
...

This code, in turn, can be rewritten as:

var i = 0;
function doSomething() {
  show[i].classList.add("visible");
  show[i].classList.remove("hide");
}

Input[i].onclick = doSomething;
i = i + 1;

Input[i].onclick = doSomething;
i = i + 1;

Input[i].onclick = doSomething;
i = i + 1;
...

What you should notice is that your function is holding onto your i box, and every time you change the contents of your i box, it impacts your function. Remember, your function is not holding onto what's inside the i box, but rather the i box itself.

This means that whenever your function is called, it will use whatever the i box has inside it at that moment.

If you add a console.log statement to your function, you can see exactly what the i box has inside it when your function is called:

for(var i = 0, max = Input.length; i < max; i++) {
  Input[i].onclick = function() {
    console.log(i);
    show[i].classList.add("visible");
    show[i].classList.remove("hide");
  }
}

Once you add that and start clicking on your buttons, you will see that i always contains the same value, regardless of which button you click! The reason is simple: you gave each button a handle to the i box, not what was inside it. That's probably not what you meant to do.


That is the first factor contributing to your error. But that's not all: the value contained by your i box when you click on a button is larger than the number of items in your list, and this is the second factor of your error. Why does this happen?

The reason this happens is because of how you are changing the contents of your i box in the context of your for loop: you are using i++, which says "increment the value of i after this round of the loop." If you think about it, that means i will always be exactly 1 + the length of your list, after your loop is finished.

Combine that with the fact that your onclick functions always use the latest value of i, and congratulations: you have an error!

Collapse
 
sanketnjain5 profile image
Sanket N Jain

Thanks man!! For finding such a hard errorπŸ˜…πŸ™ŒπŸ‘πŸ‘

Collapse
 
sanketnjain5 profile image
Sanket N Jain

Hi!!
Thank you for spending your time to solve my question. As I'm new to javascript so can you explain what "const type = event.target.dataset.methodType;" does?