Search code examples
reactjsejsmern

React log final state value after delay


I have a MERN app. On the react side, I have a state. This state may or may not change many times a second. When the state is updated, I want to send the state to my back end server API so I can save the value in my mongodb. This state can possibly change hundreds of times a second which I wish to allow. However, I only want to send this value to my server once every 5 seconds at most. This is to avoid spam and clogging my mongodb Atlas requests.

Currently, I have tried setInterval, setTimeout and even locking cpu with a while(time<endTime).

These have all posed an issue:

The setInterval is nice since I could check the currentValue with the lastSentValue and if they do not equal (!==) then I would send the currentValue to my server. Unfortunately, when I set interval, it returns the initial value that was present when the setInterval was called.

If you know how I can let a user spam a boolean button while only sending updates at most once every 5 seconds from the front end (React) to the back end (Node) and that it sends the current and up to date value then please share your thoughts and I will test them as soon as possible.

My state value is stored as::

const [aValue, anUpdate] = useState(false);

The state is changed with an onClick method returned in my React app.

function Clicked(){
    anUpdate(!aValue);
}

My set interval test looked like this::

//This is so that the button may be pressed multiple times but the value is only sent once.
const [sent, sentUpdate] = useState(false);

//inside of the clicked method

if(!sent){
    sentUpdate(true);
    setInterval(()=>{
        console.log(aValue);
    },1000);
}

My setTimeout is very similar except I add one more sentUpdate and reset it to false after aValue has been logged, that way I can log the timeout again.

//setInterval and setTimeout expected results in psudocode
aValue=true
first click->set aValue to !aValue (now aValue=false), start timeout/interval, stop setting timeouts/interval until completed
second click->set aValue to !aValue (now aValue=true), do not set timeout/interval as it is still currently waiting.
Completed timeout/interval
Log->false

//expected true as that is the current value of aValue. If logged outside of this timeout then I would receive a true value logged

In quite the opposite direction, another popular stackOverflow answer that I stumbled upon was to define a function that used a while loop to occupy computer time in order to fake the setTimeout/setInterval.

it looked like this::

function wait(ms){
    let start = new Date().getTime();
    let end = start;
    while(end < start + ms) {
        end = new Date().getTime();
    }
}

Unfortunately, when used inside of the aforementioned if statement (to avoid spam presses) my results were::

aValue=true
first click->set aValue to !aValue (now aValue=false), start wait(5000), turn off if statement so we don't call many while loops
second click->nothing happens yet - waiting for first click to end.
first click timeout->logged "false"->if statement turned back on
second click that was waiting in que is called now->set aValue to !aValue (now aValue=true), start wait(5000), turn off if statement so we don't call many while loops
second click timeout->logged "true"->if statement turned back on

So the while loop method is also not an option as it will still send every button press. It will just bog down the client when they spam click.

One more method that I saw was to use a Promise to wrap my setTimeout and setInterval however that in no way changed the original output of setTimeout/setInterval. It looked like this::

const promise = new Promise((resolve,reject)=>{
    setTimeout(()=>{
        resolve(true);
    },5000);
});
promise.then(console.log(aValue));
//I also tried resolve(aValue)->promise.then(val=>console.log(val));

Solution

  • So I had a brain blast last night and I solved it by creating a series of timeouts that cancel the previous timeout on button click and set a new timeout with the remaining value from the last timeout.

    const [buttonValue, buttonUpdate] = useState(props.buttonValue);
    const [lastButtonValue, lastButtonUpdate] = useState(props.buttonValue);
    const [newDateNeededValue, newDateNeededUpdate] = useState(true);
    const [dateValue, dateUpdate] = useState();
    const [timeoutValue, timeoutUpdate] = useState();
    
    function ButtonClicked(){
        let oldDate = new Date().getTime();
        
        if(newDateNeededValue){
          newDateNeededUpdate(false);
          dateUpdate(oldDate);
        }else{
          oldDate = dateValue;
        }
        
        //clear old timeout
        clearTimeout(timeoutValue);
        
        //check if value has changed -- value has not changed, do not set timeout
        if(lastButtonValue === !buttonValue){
          console.log("same value do not set new timout");
          buttonUpdate(!buttonValue);
          return;
        }
        
        //set timeout
        timeoutUpdate(setTimeout(()=>{
          console.log("timed out");
          lastButtonUpdate(!buttonValue);
          newDateNeededUpdate(true);
          //This is where I send to my previous file and send to my server with axios
          props.onButtonUpdate({newVal:!buttonValue});
          clearTimeout(timeoutValue);
        }, (5000-(new Date().getTime() - oldDate))));
    }