4

I'm trying to make a simple setInterval function for a typing game but it keeps glitching out depending on my syntax or not updating at all as it is now.

How do I get this to update every second and call the functions in the if statement?

const [counter, setCounter] = useState(10);

useEffect(() => {
  let timer = setInterval(() => {
    setCounter(counter - 1);

    if (counter === 0) {
      setWordIndex(wordIndex + 1);
      setLives(lives - 1);
      life.play();
      setCounter(10);
    }
  }, 1000);
}, []);

*********Edit***************

This is what I have now that is working. The first answer fixed the async issue of the counter not decrementing but I had to move the if statement outside of the useEffect to correct what I believe was caused by this same problem.

 useEffect(() => {
    let timer = setInterval(() => {
      setCounter( counter => counter - 1);
    }, 1000);
  }, []);
  if (counter == 0) {
    setWordIndex(wordIndex + 1);
    setLives(lives - 1);
    life.play();
    setCounter(10);
  }
dnolan
  • 43
  • 1
  • 5

3 Answers3

2

Use callback function in setCounter function. As you are calling the state update in an async function. it's good practice to update the state based on the previous state.

const [counter, setCounter] = useState(10);
useEffect(() => {
    let timer = setInterval(() => {
        setCounter(counter => {
            const updatedCounter = counter - 1;
            if (updatedCounter === 0) {
                setWordIndex(wordIndex + 1);
                setLives(lives - 1);
                life.play();
                return 10;
            }
            return updatedCounter;
        }); // use callback function to set the state

    }, 1000);
    return () => clearInterval(timer); // cleanup the timer
}, []);
SILENT
  • 3,916
  • 3
  • 38
  • 57
Sohail Ashraf
  • 10,078
  • 2
  • 26
  • 42
  • Thanks my counter is now updated perfectly, however my if statement functions aren't firing when the count reaches 0. Any thoughts? thanks again! – dnolan Jan 07 '20 at 03:39
  • 1
    Figured it out! I had to move the if statement outside of the useEffect because of this same async issue. Thanks! – dnolan Jan 07 '20 at 03:47
  • @dnolan If you move it outside of the useEffect, it will refresh twice. This answer is also prone to memory leaks. Look at my answer. – SILENT Jan 07 '20 at 03:50
  • Use the logic inside the state update and, so it will run only when the state changes. But don't update state inside state. – Sohail Ashraf Jan 07 '20 at 03:54
  • @Sohail even if you copied my answer, you still made a mistake. Fixing it... – SILENT Jan 07 '20 at 04:25
  • Thank for highlighting the issue and fixings it. – Sohail Ashraf Jan 07 '20 at 04:28
1

The previous answers aren't considering the counter values aren't immediately updating. They are also prone memory leaks since setInterval isn't cleared.

const [counter, setCounter] = useState(10);
useEffect(() => {
  let timer = setInterval(() => {
    setCounter( counter => {
        const nC = counter - 1;
        if (nC === 0) {
           setWordIndex(wordIndex + 1);
           setLives(lives - 1);
           life.play();
           return 10;
        }
        return nC;
     });
  }, 1000);
  return () => clearInterval(timer);
}, []);
SILENT
  • 3,916
  • 3
  • 38
  • 57
0

The previous answers weren't considering other states - wordIndex and lives and didn't include clear Intervals

It's advisable to use callback setState inside setIntervals and clear the interval on next useEffect call

  const [counter, setCounter] = React.useState(10);
  React.useEffect(() => {
    let timer = setInterval(() => {
      // It's advisable to use callback setState inside setIntervals
      setCounter(prev => {
        if (prev !== 0) return prev - 1;

        setWordIndex(wordIndex + 1);
        setLives(lives - 1);
        life.play();    
        return 10;
      });
    }, 1000);

    // And clear the interval next useEffect call
    return () => clearInterval(timer);
  }, [wordIndex, lives]);
Pushkin
  • 3,336
  • 1
  • 17
  • 30