Reputation: 55
Trying to set the innerHTML of a HTML class which are four boxes, each to be set 3 seconds one after another. I can set the innerHTML without setTimeout when the innerHTML is set to a loading icon. When innerHTML is put inside setTimeout the following is returned: 'Uncaught TypeError: Cannot set property 'innerHTML' of undefined'.
Tried to debug my code sending messages to the console and searching stackoverflow but no luck.
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < 4; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
Would like to know why my innerHTML cannot be set within setTimeout here and possible solutions to my problem.
Upvotes: 5
Views: 1387
Reputation: 8621
This issue appears to be caused by several factors.
generateRandomNumbers()
function outside the setTimeout()
scope.var
definition inside your for
loop.function generateRandomNumbers() {
return Math.floor(Math.random() * 999999) + 10000;
}
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < x.length; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
for (let i = 0; i < x.length; i++) {
setTimeout(function() {
x[i].innerHTML= '';
x[i].innerHTML = generateRandomNumbers();
}, 3000 * i);
}
This is a different implementation, but here's a Fiddle
Upvotes: 0
Reputation: 33
You are trying to access x
in a inner method, that is x is not defined in the scope of setTimeout
that is why you receive that execption
I would suggest you use a setInterval
function as the solution
Your code:
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
A work around
var randomNums = generateRandomNumbers();
let i = 0;
let interval = setInterval(function() {
if( i != 2){
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
i += 1;
} else {
clearInterval(interval) ;
}
}, 3000);
Upvotes: 0
Reputation: 16908
When you use var
inside the for-loop the setTimeout
is actually triggered for the last value of i
as in var
the binding happens only once.
This is because the setTimeout
is triggered when the entire loop is completed, then your i
will be 4
. Keep in mind that there is a closure because of the callback function you pass in the setTimeout
call. That closure will now refer to the final value of i
which is 4.
So in this case when the complete loop has executed the value of i
is 4
but there are indexes upto 3
in x. That is why when you try to access x[4]
you get undefined
and you see TypeError
to fix this just use let
for fresh re-binding with the new value of i
in every iteration:
for (let i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
Also if you cannot use let
due to browser incompatibility you can do the trick with a IIFE:
for (var i = 0; i < 4; i++) {
(function(i){
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
})(i);
}
This works because var
has function scope, so here in every iteration a new scope would be created along with the function with a new binding to the new value of i
.
Upvotes: 3
Reputation: 542
This problem is related to a very basic and popular concept of Javascript called closure. It can be solved in at least two ways:
let
var x = document.getElementsByClassName("numberBox");
for (let j = 0; j < 4; j++) {
x[j].innerHTML= '';
x[j].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (let i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i);
}
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < 4; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout((function (j) {
x[j].innerHTML= '';
x[j].innerHTML = randomNums[j];
})(i), 3000 * i);
}
Upvotes: 0
Reputation: 2249
Because of missing i parameter - timeout probably used last (4) which was out of array.
But you can set&use function parameters by adding next timeout parameters.
var x = document.getElementsByClassName("numberBox");
for (var i = 0; i < 4; i++) {
x[i].innerHTML= '';
x[i].innerHTML= "<div class='loader'></div>"
}
// function to generate array of 4 random numbers
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function (i) {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i, i);
}
function generateRandomNumbers() {
var retVal = [];
for (var i = 0; i < 4; i++) {
retVal.push(Math.random());
}
return retVal;
}
<div class="numberBox"></div>
<div class="numberBox"></div>
<div class="numberBox"></div>
<div class="numberBox"></div>
Upvotes: 1
Reputation: 453
// function to generate array of 4 random numbers
var x = document.getElementsByClassName("numberBox");
var randomNums = generateRandomNumbers();
for (var i = 0; i < 4; i++) {
setTimeout(function () {
x[i].innerHTML= '';
x[i].innerHTML = randomNums[i];
}, 3000 * i, x);
}
Upvotes: 0
Reputation: 454
I believe this is a question of the current scope. The setTimeout function creates its own scope that has no reference to the old variable. You'll likely need to redefine what x is inside the timeout or pass the array explicitly to the timeout.
See here for how-to: How can I pass a parameter to a setTimeout() callback?
I would also recommend reading up on closers as well: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures
Upvotes: 3