23

Building a Sort-Visualizer in React using the Create-React-App [https://roy-05.github.io/sort-visualizer/ ]

I'm animating each iteration of the loop using setTimeouts. On dev console I get the following warning:

Line 156:32: Function declared in a loop contains unsafe references to variable(s) 'minimum', 'minimum', 'minimum', 'minimum' no-loop-func

Here's the code-snippet:

for(let i=0; i<arr.length-1; i++){
            let minimum = i; //Declare minimum here
            setTimeout(()=>{
                for(let j = i+1; j<arr.length; j++){
                    setTimeout(()=>{
                        //Getting a warning for these references:
                        array_bar[j].style.backgroundColor = 'red';
                        array_bar[minimum].style.backgroundColor = 'blue';
                        setTimeout(()=>{
                            if(arr[j] < arr[minimum]){
                            array_bar[minimum].style.backgroundColor = 'lightblue';
                            minimum = j; 
                            }  
                            else{
                                array_bar[j].style.backgroundColor = 'lightblue';
                            }  
                        }, 4);
                    }, (j-1)*4);    
                }

Going through ESLint Docs, I believe the issue might be that i'm modifying the value inside the setTimeout but the variable is declared outside its scope.

I'm not sure how to fix that warning, any help will be appreciated!

Note: Here's the entire function if you need it -

selectionSort(){
        const arr = this.state.array,
            array_bar = document.getElementsByClassName("array-elem");

        this.setState({startedSelectionSort: true});

        for(let i=0; i<arr.length-1; i++){
            let minimum = i; //Declare minimum here
            setTimeout(()=>{
                for(let j = i+1; j<arr.length; j++){
                    setTimeout(()=>{
                        //Getting a warning for these references:
                        array_bar[j].style.backgroundColor = 'red';
                        array_bar[minimum].style.backgroundColor = 'blue';
                        setTimeout(()=>{
                            if(arr[j] < arr[minimum]){
                            array_bar[minimum].style.backgroundColor = 'lightblue';
                            minimum = j; 
                            }  
                            else{
                                array_bar[j].style.backgroundColor = 'lightblue';
                            }  
                        }, 4);
                    }, (j-1)*4);    
                }
                setTimeout(()=>{
                    let temp = arr[i],
                    arr1_height = arr[minimum],
                    arr2_height = arr[i];

                    arr[i] = arr[minimum];
                    arr[minimum] = temp;

                    array_bar[i].style.height = `${arr1_height}px`;
                    array_bar[minimum].style.height = `${arr2_height}px`;

                    array_bar[i].style.backgroundColor = "green";
                    if(i !== minimum){
                        array_bar[minimum].style.backgroundColor = 'lightblue';
                    }
                }, 400);


                if(i === arr.length-2){
                    setTimeout(()=>{
                        array_bar[i+1].style.backgroundColor = "green";
                    },800);
                }

            }, i*400);
        }

        setTimeout(()=>{
            this.setState({sorted: true})
        }, arr.length*400+1750);

    }
roy05
  • 381
  • 1
  • 2
  • 11
  • Could you try initializing minimum inside your for loop? For example: for (let i, minimum = 0; i < arr.length - 1; i++) – landesko Oct 18 '21 at 18:59

4 Answers4

31

I also encountered same warning. In my case, I declared variable outside the iteration, but modified variable inside forEach method.

Something like:

// some code above
let validInputs = true;

someInputs.forEach( input => {
  validInputs = input.value && validInputs;
})

After I did some reserch, I found in this post, JSHint error : Functions declared within loops referencing an outer scoped variable may lead to confusing semantics, mentioned that JSHint doesn't like how the anonymous function in there is being re-created over and over.

I changed forEach arrow function to for (let index i = 0; index < someInputs.length; index++), and the warning is gone.

Perhaps in your case, change setTimeout to traditional non-arrow function can remove the warning.

updated on Apr 7th 2021

As I'm reading the Professional JavaScript for Web Developers, 4th edition, I might have found why this warning is implemented in the ESLint.

From section 4.3 Garbage Collection sections, the book mentioned that closure might also lead to memory leak.

The purpose for forEach and arrow function is to limit the scope of the variable, as describes below from MDN:

Arrow functions establish "this" based on the scope the Arrow function is defined within. from Arrow function expressions

In section Creating closures in loops: A common mistake, MDN mentioned:

Another alternative could be to use forEach() to iterate over the helpText array and attach a listener to each , as shown:

function showHelp(help) {
  document.getElementById('help').textContent = help;
}

function setupHelp() {
  var helpText = [
      {'id': 'email', 'help': 'Your e-mail address'},
      {'id': 'name', 'help': 'Your full name'},
      {'id': 'age', 'help': 'Your age (you must be over 16)'}
    ];

  helpText.forEach(function(text) {
    document.getElementById(text.id).onfocus = function() {
      showHelp(text.help);
    }
  });
}

setupHelp();

In our implementation, calling arrow functions inside forEach is creating closure of closure, which obviously can create some confusing semantics for garbage collection.

GoldenArcher
  • 812
  • 1
  • 9
  • 12
  • `eslint` also doesn't like `index++` – sujeet Apr 21 '20 at 04:57
  • 4
    `can create some confusing semantics for garbage collection` - [Please don't weasel](https://en.wikipedia.org/wiki/Weasel_word). No offense but that sentence sounds like someone is talking about something they know very little about. I'd recommend either making a precise statement as to how a GC can be "confused", or removing that statement altogether. In the MDN example, a memory leak can occur because the function is assigned to a DOM node, which is a very different scenario from yours. – Domi Oct 10 '21 at 15:59
2

You're correct that modifying the variable inside setTimeout is causing the issue. You can get around this by wrapping setTimeout inside a promise and waiting for it to resolve before modifying your variables. This is much cleaner using async/await:

for (let i = 0; i < arr.length - 1; i++) {
    let minimum = i;
    await new Promise(resolve => setTimeout(resolve, i * 400));
    for (let j = i + 1; j < arr.length; j++) {
        array_bar[j].style.backgroundColor = "red";
        array_bar[minimum].style.backgroundColor = "blue";

        await new Promise(resolve => setTimeout(resolve, (j - 1) * 400));
        if (arr[j] < arr[minimum]) {
            array_bar[minimum].style.backgroundColor = "lightblue";
            minimum = j;
        }
    }
}

With each loop, you're creating a promise that resolves once the timeout is expired. Using await will pause execution of your function until the promise resolves. Then, you can modify variables like minimum because they are no longer within the scope of the callback function you were originally passing into setTimeout.

2

Using typescript and React, I was able to initialize minimum inside of the for loop call, and then reinitialize once I got inside:

for (let i, minimum = 0; i < arr.length - 1; i++) {
  minimum = i; //reinitialize minimum here
  setTimeout(() => {
    for (let j = i + 1; j < arr.length; j++) {
      setTimeout(() => {
        //Getting a warning for these references:
        array_bar[j].style.backgroundColor = "red";
        array_bar[minimum].style.backgroundColor = "blue";
        setTimeout(() => {
          if (arr[j] < arr[minimum]) {
            array_bar[minimum].style.backgroundColor = "lightblue";
            minimum = j;
          } else {
            array_bar[j].style.backgroundColor = "lightblue";
          }
        }, 4);
      }, (j - 1) * 4);
    }
  });
}
landesko
  • 373
  • 1
  • 5
  • 16
1

For me redeclaring the variables in the timeout function did remove that warning for me in FirebaseFunctions.

setTimeout(async ()=> {
    var NumberInst = await admin
     .firestore()
     .collection("CollName")
     .doc('DocName')
     .get();
    var Numbers = NumberInst.data().postponeX;
 }, 1000 * 60 * 11 ); 
Saad
  • 3,340
  • 2
  • 10
  • 32
Shalabyer
  • 555
  • 7
  • 23