Thiago Carvalho
Thiago Carvalho

Reputation: 105

How can I start / stop setInterval?

I've tried different ways, but It doesn't works.

[...]
  const [automatic, setAutomatic] = useState(false);

[...]
  var startAuto;

  useEffect(() => {
    if (!automatic) {
      console.log("stop");
      clearInterval(startAuto);
    } else {
      startAuto = setInterval(() => {
        changeQuestion("+");
      }, 5 * 1000);
    }
  }, [automatic]);

[...]
        <Button
          onPress={() => setAutomatic(!automatic)}
          title="turn on/off"
        />
[...]

It works when I put a setTimeout outside the useEffect, that way:

setTimeout(() => { clearInterval(startAuto); alert('stop'); }, 10000);

But I want to have a button to start / stop

Upvotes: 2

Views: 113

Answers (2)

Ori Drori
Ori Drori

Reputation: 191976

Your var startAuto; is redeclared on each render, and since changing the state causes a re-render, it never holds the reference to the interval, which is never cleared.

Use the useEffect cleanup function to clear the interval. Whenever automatic changes, it would call the cleanup (if returned by the previous invocation), and if automatic is true it would create a new interval loop, and return a new cleanup function of the current interval.

useEffect(() => {
  if(!automatic) return;
  
  const startAuto = setInterval(() => {
    changeQuestion("+");
  }, 5 * 1000);

  return () => {
    clearInterval(startAuto);
  };
}, [automatic]);

Working example:

const { useState, useEffect } = React;

const Demo = () => {
  const [automatic, setAutomatic] = useState(false);
  const [question, changeQuestion] = useState(0);
  
  useEffect(() => {
    if(!automatic) return;
    
    const startAuto = setInterval(() => {
      changeQuestion(q => q + 1);
    }, 5 * 100);

    return () => {
      clearInterval(startAuto);
    };
  }, [automatic]);

  return (
    <div>
      <button
        onClick={() => setAutomatic(!automatic)}
      >
        turn {automatic ? 'off' : 'on'}
      </button>
      
      <p>{question}</p>
    </div>
  );
}

ReactDOM
  .createRoot(root)
  .render(<Demo />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>

<div id="root"></div>

Upvotes: 2

Kirill Novikov
Kirill Novikov

Reputation: 3067

For example, you can check and use this hook:

https://usehooks-ts.com/react-hook/use-interval

export default function Component() {
  // The counter
  const [count, setCount] = useState<number>(0)
  // Dynamic delay
  const [delay, setDelay] = useState<number>(1000)
  // ON/OFF
  const [isPlaying, setPlaying] = useState<boolean>(false)

  useInterval(
    () => {
      // Your custom logic here
      setCount(count + 1)
    },
    // Delay in milliseconds or null to stop it
    isPlaying ? delay : null,
  )

  const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
    setDelay(Number(event.target.value))
  }

  return (
    <>
      <h1>{count}</h1>
      <button onClick={() => setPlaying(!isPlaying)}>
        {isPlaying ? 'pause' : 'play'}
      </button>
      <p>
        <label htmlFor="delay">Delay: </label>
        <input
          type="number"
          name="delay"
          onChange={handleChange}
          value={delay}
        />
      </p>
    </>
  )
}

Upvotes: 1

Related Questions