setState is updating immediately even after using setTimeOut

I have a div with 10 elements, which are to be updated one-by-one with a time delay of say 2secs. Below is the code for the same

for(let boxNo=0; boxNo<10; boxNo++){
  setTimeout(() => {
    nodes[boxNo].isMarked = true;
    this.setState({nodes});
    }, (boxNo*200)+boxNo);
  );
}

But when i run it, all the elements are updated together. The program just adds a delay add the beginning, and all the elements are updated (isMarked) together. How can I make the code to mark the elements one-by-one?

Answers:

Answer

You're breaking two of the fundamental rules of React:

  1. Don't mutate state directly

    for(let boxNo=0; boxNo<10; boxNo++){
      setTimeout(() => {
        nodes[boxNo].isMarked = true; // <==== here
        this.setState({nodes});
        }, (boxNo*200)+boxNo);
      );
    }
    
  2. If updating state based on existing state, use the callback form because state updates may be asynchronous (and in any case, in your example, time has passed):

    for(let boxNo=0; boxNo<10; boxNo++){
      setTimeout(() => {
        nodes[boxNo].isMarked = true;
        this.setState({nodes});       // <==== here
        }, (boxNo*200)+boxNo);
      );
    }
    

Instead, see *** comments and associated code:

// **IF** `nodes` is an array
for(let boxNo=0; boxNo<10; boxNo++){
  setTimeout(() => {
    // *** Note using callback form (#2)
    this.setState(({nodes} => {
        // *** *Copy* the parts of state you're going to modify (#1)
        nodes = [...nodes];
        nodes[boxNo] = {...nodes[boxNo], isMarked: true};
        return {nodes};
    });
    }, (boxNo*200)+boxNo);
  );
}

that setState call can also be written like this at the (trivial) expense of creating a temporary object:

this.setState(({nodes} => ({
    nodes: Object.assign([], nodes, {[boxNo]: {...nodes[boxNo], isMarked: true}})
});

or

// **IF** `nodes` is a non-array object
for(let boxNo=0; boxNo<10; boxNo++){
  setTimeout(() => {
    // *** Note using callback form (#2)
    this.setState(({nodes} => {
        // *** *Copy* the parts of state you're going to modify (#1)
        return {
            nodes: {
                ...nodes,
                [boxNo]: {...nodes[boxNo], isMarked: true}
            }
        };
    });
    }, (boxNo*200)+boxNo);
  );
}
Answer

This works for fine for me.

 (function loop (boxNo) {          
  setTimeout(function () {   
     nodes[boxNo].isMarked = true;
     this.setState({nodes});                
     if (--boxNo) loop(boxNo);    // decrementing i and calling loop function again if i > 0
  },  ((boxNo+1)*2000)+boxNo)
})(10);
Answer

setTimeout is calculating the 200 in your loop first time.

try to increase the time with more milliseconds.

3 seconds (3000 milliseconds)

    for(let boxNo=0; boxNo<10; boxNo++){
(function(x){
      setTimeout(function(){
        nodes[boxNo].isMarked = true;
        this.setState({nodes});
        }, ((x+1)*3000)+x);}
      )(boxNo);
    }

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us Javascript

©2020 All rights reserved.