Sacha
Sacha

Reputation: 3

The Problem is :Cannot read property 'play' of undefined

I'm actually stuck solving this problem. It gives me this error "Uncaught TypeError: Cannot read property 'play' of undefined". When i used foreach loop everything goes fine but using normal for-loop gives me this error and i don't know why.

here is the code using javascript

window.addEventListener("load", function () {
    const pads = document.querySelectorAll(".pads div");
    const sounds = document.querySelectorAll(".sound");
    var numberOfPads = document.querySelectorAll(".pads div").length;

    for (var i = 0; i < numberOfPads; i++) {
        pads[i].addEventListener("click", function () {
            sounds[i].play();
        });

    }

});

HTML CODE:

<body>
    <section class="app">
        <header>
            <h1>Beatmaker</h1>
        </header>
        <div class="visual">
            <div class="pads">
                <div class="pad1">
                    <audio class="sound" src="sounds/bubbles.mp3">
                    </audio>
                </div>
                <div class="pad2">
                    <audio class="sound" src="sounds/clay.mp3">
                    </audio>
                </div>
                <div class="pad3">
                    <audio class="sound" src="sounds/confetti.mp3">
                    </audio>
                </div>
            </div>
            <div class="pads">
                <div class="pad4">
                    <audio class="sound" src="sounds/glimmer.mp3">
                    </audio>
                </div>
                <div class="pad5">
                    <audio class="sound" src="sounds/moon.mp3">
                    </audio>
                </div>
                <div class="pad6">
                    <audio class="sound" src="sounds/ufo.mp3">

                    </audio>
                </div>
            </div>
        </div>
    </section>
</body>

Upvotes: 0

Views: 2338

Answers (1)

CherryDT
CherryDT

Reputation: 29012

This is a tricky one. It happens because i is function-scoped, meaning that one and the same i is used for any point inside your function. This also applies to things running in the future. The loop modifies i, and when the loop is done, i will be 1 higher than the maximum value.

Let's go through this step by step - in the order in which things happen. For clarity: I see 6 divs, so we have valid values of i from 0 to 5 (including).

  • Your load event handler executes:
    • i is 0. Your loop body runs because 0 is less than 6.
    • The statement pads[i].addEventListener("click", ...) executes with i == 0
    • i is 1. Your loop body runs because 1 is less than 6.
    • The statement pads[i].addEventListener("click", ...) executes with i == 1
    • ...
    • i is 5. Your loop body runs because 5 is less than 6.
    • The statement pads[i].addEventListener("click", ...) executes with i == 5
    • i is 6. Your loop body doesn't run because 6 is not less than 6.
  • You click a div, say pad4.
    • The click handler runs (the one that was set when i was 3).
    • However, since we always talked about the same i here, i is now 6 (see the last step from before!).
    • The line sounds[i].play() runs, attempting to access sounds[6]
    • Since sounds[6] is undefined, you cannot read its property play and you get an error.

The reason it works with a forEach is that it uses a callback, and that means that the i you access in there is the argument to your callback function, which is local to the specific function invocation. So you essentially get a new i every time, which keeps its value also in the future (it is never modified). You essentially get 6 different is this way, not 1 i that changes its value over time!

If you want to keep using a for loop, you have to use let instead of var:

for (let i = 0; i < numberOfPads; i++) {
  // ...
}

This is because let creates a block-scoped variable. I know it is a bit confusing now how this can work, since i is still getting modified, but this is due to a special convenience behavior with for in combination with let: It actually creates two is here, one that is modified every time (the one you see inside the for (...) parens) and one that is created as copy from the modified one on every iteration (the one you see inside the loop body).

If you are getting confused by this, another way to (more logically) solve the problem would be to manually assign i to a block-scoped copy inside the loop:

window.addEventListener("load", function () {
    const pads = document.querySelectorAll(".pads div");
    const sounds = document.querySelectorAll(".sound");
    var numberOfPads = document.querySelectorAll(".pads div").length;

    for (var i = 0; i < numberOfPads; i++) {
        const j = i; // Create local copy
        pads[j].addEventListener("click", function () {
            sounds[j].play(); // Now we use j here and not i
        });

    }

});

(I used const to emphasize that it won't and even can't be modified, but let would work as well. Just var won't!)

Upvotes: 1

Related Questions