Home > Net >  How do I update a state each second for next n seconds
How do I update a state each second for next n seconds

Time:01-15

I have a state variable resendTime and magicLinkState.

I would like to setResendTime to resendTime - 1 for next 10 seconds when magicLinkState becomes sent.

I have tried the following code but this does not update resendTime every second.

useEffect(() => {
  if (magicLinkState === "sent" && resendTime > 0) {
    const timer = setInterval(() => setResendTime(resendTime - 1), 1000);
    console.log(resendTime);
    return () => clearInterval(timer);
  }
}, []);

This code only updates the state for the first second. I have also tried passing dependency array but didn't worked. How do I make it update the state so I achieve what is intended.

CodePudding user response:

The general idea is correct in your code but needs some adjustments.

  1. it's required to add the magicLinkState to the effect's dependencies otherwise it won't trigger when this gets the sent value but only if it's initially set with this value.
  2. you should use the arrow syntax: setResendTime((prevResendTime) => prevResendTime - 1) in order to grab the correct state value each time
  3. there is no need for a cleanup function in this effect since you want to clear the interval only after it's triggered and made 10 decreases to resendTime.
  4. you should add some local count variable in order for the decrease to happen 10 times only and not forever

After these changes your code should look like below:

 const decreaseResentTime = (prevResendTime) => prevResendTime - 1;

 useEffect(() => {
    let timer;
    if (magicLinkState === "sent" && resendTime > 0) {
      let count = 0;
      timer = setInterval(() => {
        if (count < 10) {
          count  ;
          setResendTime(decreaseResentTime );
        } else {
          clearInterval(timer);
        }
      }, 1000);
    }
  }, [magicLinkState]);

You can find an example that demonstrates this solution in this CodeSandbox.

There are more improvements to be made here but they are based on your needs. For example if the magicLinkState changes to 'sent' and then to something else and then back to 'sent' within the 10 seconds, 2 intervals will run and decrease at a double rate.

CodePudding user response:

You've just an issue of a stale closure over the initial resendTime state value. This is easily fixed by using a functional state update to correctly access the previous state value instead of whatever is closed over in the surround callback scope.

const timerRef = React.useRef();

useEffect(() => {
  if (magicLinkState === "sent" && resendTime > 0) {
    const timer = setInterval(() => {
      setResendTime(resendTime => resendTime - 1); // <-- functional update
    }, 1000);
    timerRef.current = timer;
    
    return () => clearInterval(timer);
  }
}, []);

Note also that because of the closure that if you want to log the resendTime state as it updates you'll need to use a another useEffect hook with a proper dependency. This is also where you'd move the logic to check if the interval is completed. Use a React ref to store a reference to the timer id.

useEffect(() => {
  if (resendTime <= 0) {
    clearInterval(timerRef.current);
  }
  console.log(resendTime);
}, [resendTime]);

CodePudding user response:

Can I use timestamps instead of intervals? this function checks every second for 10 seconds when called once

useEffect(() => {
  if (magicLinkState === "sent" && resendTime > 0) {
    let startTimeforTenSeconds = Date.now();
    let startTimeforEachSecond = Date.now();

    const nonBlockingCommand = new Promise((resolve, reject) => {
        while(true) {
            const currentTime = Date.now();

            if (currentTime - startTimeforEachSecond >= 1000) {
              startTimeforEachSecond = Date.now();
              console.log("1 seconds have passed. set resend time here");
              // code for resending time here
              setResendTime(resendTime - 1);
              console.log(resendTime);
            }

            if (currentTime - startTimeforTenSeconds >= 10000) {
              console.log("10 seconds have passed. stop checking.");
              resolve("10 seconds have passed.");
              break;
            }
        }
    });

    nonBlockingCommand();
  }
}, []);
  • Related