Search code examples
javascriptreactjsreact-hookssetintervaluse-effect

React: ClearInterval and Immediately Start Again


I have a component that sets off a timer which updates and makes an axios request every 30 seconds. It uses a useRef which is set to update every 30 seconds as soon as a function handleStart is fired.


  const countRef = useRef(null);
  const lastUpdatedRef = useRef(null);
  const [lastUpdated, setLastUpdated] = useState(Date.now())

  const handleStart = () => {

    countRef.current = setInterval(() => {
      setTimer((timer) => timer + 1);
    }, 1000);

    lastUpdatedRef.current = setInterval(() => {
      setLastUpdated(Date.now());
    }, 30000);
  };

Now I have a useEffect that runs a calculate function every 30 seconds whenever lastUpdated is triggered as a dependency:

  const firstCalculate = useRef(true);

  useEffect(() => {
    if (firstCalculate.current) {
      firstCalculate.current = false;
      return;
    }
    console.log("calculating");
    calculateModel();
  }, [lastUpdated]);

This updates the calculate function every 30 seconds (00:30, 01:00, 01:30 etc.) as per lastUpdatedRef. However, I want the timer to restart from when lastUpdated state has been modified elsewhere (e.g. if lastUpdated was modified at 00:08, the next updated will be 00:38, 01:08, 01:38 etc.). Is there a way to do this?


Solution

  • Basically it sounds like you just need another handler to clear and restart the 30 second interval updating the lastUpdated state.

    Example:

    const handleOther = () => {
      clearInterval(lastUpdatedRef.current);
      lastUpdatedRef.current = setInterval(() => {
        setLastUpdated(Date.now());
      }, 30000);
    }
    

    Full example:

    const calculateModel = () => console.log("calculateModel");
    
    export default function App() {
      const countRef = React.useRef(null);
      const lastUpdatedRef = React.useRef(null);
      const [lastUpdated, setLastUpdated] = React.useState(Date.now());
      const [timer, setTimer] = React.useState(0);
    
      const handleStart = () => {
        countRef.current = setInterval(() => {
          setTimer((timer) => timer + 1);
        }, 1000);
    
        lastUpdatedRef.current = setInterval(() => {
          setLastUpdated(Date.now());
        }, 30000);
      };
    
      const handleOther = () => {
        clearInterval(lastUpdatedRef.current);
        lastUpdatedRef.current = setInterval(() => {
          setLastUpdated(Date.now());
        }, 30000);
      };
    
      const firstCalculate = React.useRef(true);
    
      React.useEffect(() => {
        if (firstCalculate.current) {
          firstCalculate.current = false;
          return;
        }
        console.log("calculating");
        calculateModel();
      }, [lastUpdated]);
    
      return (
        <div className="App">
          <h1>Hello CodeSandbox</h1>
          <h2>Start editing to see some magic happen!</h2>
    
          <div>Timer: {timer}</div>
    
          <button type="button" onClick={handleStart}>
            Start
          </button>
          <button type="button" onClick={handleOther}>
            Other
          </button>
        </div>
      );
    }
    

    Edit react-clearinterval-and-immediately-start-again

    Don't forget to clear any running intervals when the component unmounts!

    React.useEffect(() => {
      return () => {
        clearInterval(countRef.current);
        clearInterval(lastUpdatedRef.current);
      };
    }, []);