Home > Software design >  How to stop a loop that modifies state in react?
How to stop a loop that modifies state in react?

Time:04-05

I was trying to build a simple counter application, the app starts counting ( 1/sec) when I click the start button and stops when I click the stop button.

I came up with 2 different solutions, one using setTimeout and the other using a for loop with delay. both these solutions work for incrementing the number. Are both of these valid react ways for creating a counter? is there a better way to do this?

When stopping the app I can stop it by changing the reference variable halt.current = true but changing the state setStop(true) does nothing, why is that?

function App() {
  const [get, set] = useState(0);
  const [stop, setStop] = useState(false);
  const halt = useRef(false);

  const loopfn = async () => {
    halt.current = false;
    setStop(false);
    while (true) {
      if (halt.current || stop) break;
      await new Promise((res) => setTimeout(res, 1000));
      set((prev: number) => prev   1);
    }
  };

  const timeoutloopfn = () => {
    halt.current = false;
    setStop(false);
    setTimeout(() => {
      if (halt.current || stop) return;
      set((prev: number) => prev   1);
      timeoutloopfn();
    }, 1000);
  };

  const stoploopref = () => {
    halt.current = true;
  };

  const stoploopst = () => {
    setStop((prev: boolean) => true);
  };

  return (
    <div>
      <button onClick={loopfn}>for-loop increment</button>
      <button onClick={timeoutloopfn}>timeout increment</button>
      <button onClick={stoploopref}>stop using ref</button>
      <button onClick={stoploopst}>stop using state</button>
      <button onClick={() => set(0)}>reset</button>
      <p>{get}</p>
    </div>
  );
}

CodePudding user response:

You may consider using the setInterval function instead, storing its id in a state and clearing it when stop is set to true:

function App() {
  const [get, set] = useState(0);
  const [stop, setStop] = useState(false);
  const [intervalId, setIntervalId] = useState(-1);
  
  const halt = useRef(false);

  useEffect(() => {
    // Stop the loop
    if (stop && intervalId !== -1) {
        clearInterval(intervalId)   
        setIntervalId(-1)
    }
  }, [stop])

  const timeoutloopfn = () => {
    halt.current = false;
    setStop(false);
    const newIntervalId = setInterval(() => {
      set((prev: number) => prev   1);
    }, 1000);
    setIntervalId(newIntervalId)
  };

CodePudding user response:

I would totally recommend useEffect for every function that requires timing. import React, { useRef, useEffect, useState } from "react";

export default function App() {
   const [number, setNumber] = useState(100);

   let intervalRef = useRef();

   const decreaseNum = () => setNumber(prev => prev - 1);

   useEffect(() => {
      intervalRef.current = setInterval(decreaseNum, 1000);
      return () => clearInterval(intervalRef.current);
   }, []);

   return <div>{number}</div>;
}
  • Related