Cannot set innerHTML inside setTimeout
Asked Answered
S

7

5

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.

Schinica answered 29/8, 2019 at 18:4 Comment(2)
Understand closures and setTimeout as pointed out by adr5240Tympan
the short version of these answers is that var is horrible and broken, and you should always use let insteadLeath
C
3

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

Common answered 29/8, 2019 at 18:9 Comment(0)
N
3

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.

Nemesis answered 29/8, 2019 at 18:13 Comment(0)
V
1

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>
Violetteviolin answered 29/8, 2019 at 18:14 Comment(0)
T
0
    // 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);
    }
Tympan answered 29/8, 2019 at 18:11 Comment(0)
C
0

This problem is related to a very basic and popular concept of Javascript called closure. It can be solved in at least two ways:

  1. Using 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);
}
  1. Using IIFE

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);
}

Caldeira answered 29/8, 2019 at 18:19 Comment(0)
J
0

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);
Jonette answered 29/8, 2019 at 19:6 Comment(0)
D
0

This issue appears to be caused by several factors.

  • Defining the generateRandomNumbers() function outside the setTimeout() scope.
  • Using the 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

Deceitful answered 29/8, 2019 at 19:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.