React hooks useInterval reset after button click
Asked Answered
H

4

5

I have hook useInterval which download data every 10 seconds automaticaly, however I have also button which can manually download data in every moment. I'm struggling to restart interval timer when I click button. So basically if interval counts to 5, but I click button meantime, interval should restart and starts counting to 10 again before downloading data

const useInterval = (callback, delay) => {
    const savedCallback = useRef(callback);

    useEffect(() => {
        savedCallback.current = callback;
    }, [callback]);

    useEffect(() => {
        const tick = () => {
            savedCallback.current();
        }

        if (delay !== null) {
            const id = setInterval(tick, delay);
            return () => clearInterval(id);
        }
    }, [delay]);
};

export default useInterval;

APP PART:

useInterval(() => {
        getMessage();
      }, 10000)

const getMessage = async () => {
    setProcessing(true)
    try {
      const res = await fetch('url')
      const response = await res.json();
      setRecievedData(response)
    }
    catch (e) {
      console.log(e)
    }
    finally {
      setProcessing(false)
    }
  }

const getMessageManually = () => {
    getMessage()
    RESTART INTERVAL
  }
Haemorrhage answered 4/2, 2021 at 9:55 Comment(0)
H
5

You should add a reset function as returning a value from the hook.

I also fixed few issues and added an unmount handler:

// Usage
const resetInterval = useInterval(() => ..., DELAY);
resetInterval(); 
// Implementation
const useInterval = (callback, delay) => {
  const savedCallbackRef = useRef(callback);

  const intervalIdRef = useRef();

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // handle tick
  useEffect(() => {
    const tick = () => {
      savedCallback.current();
    };

    if (delay !== null) {
      intervalIdRef.current = setInterval(tick, delay);
    }

    const id = intervalIdRef.current;
    return () => {
      clearInterval(id);
    };
  }, [delay]);

  // handle unmount
  useEffect(() => {
    const id = intervalIdRef.current;
    return () => {
      clearInterval(id);
    };
  }, []);

  const resetInterval = useCallback(() => {
    clearInterval(intervalIdRef.current);
    intervalIdRef.current = setInterval(savedCallback.current, delay)
  }, [delay]);

  return resetInterval;
};
Hatchel answered 4/2, 2021 at 10:9 Comment(1)
Reset actually stop but doesn't start new interval but I figured out how to do so thanks to your answer and now everything works fine. Thank you!Haemorrhage
I
5

You can add a reset function in the hook and return that function. The reset function should clear the existing interval and start a new one.

Here is the code for the hook which can be reset and stopped.

const useInterval = (callback, delay) => {
  const savedCallback = useRef(callback);
  const intervalRef = useRef(null);

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (delay !== null) {
      const id = setInterval(savedCallback.current, delay);
      intervalRef.current = id;
      return () => clearInterval(id);
    }
  }, [delay]);

  useEffect(()=>{
    // clear interval on when component gets removed to avoid memory leaks
    return () => clearInterval(intervalRef.current);
  },[])

  const reset = useCallback(() => {
      if(intervalRef.current!==null){
        clearInterval(intervalRef.current);
        intervalRef.current = setInterval(savedCallback.current,delay)
      }
   });

   const stop = useCallback(() => {
      if(intervalRef.current!==null){
        clearInterval(intervalRef.current);
      }     
   })

  return {
    reset,
    stop
  };
};
    
// usage
const {reset,stop} = useInterval(()=>{},10000);
reset();
stop();
Irate answered 4/2, 2021 at 10:21 Comment(2)
Just few mistakes: clearInterval(intervalRef.current) - You don't have the ref on unmount, intervalRef!==null is always truthyHatchel
@DennisVash Thanks. Corrected. But clearInterval(intervalRef.current) works. What's wrong here.Irate
P
0

Another solution is to remove the ref on the callback making the hook restart the count on every change to the callback

so updating the above solution

// Implementation const useInterval = (callback, delay) => {

const intervalIdRef = useRef();

// handle tick useEffect(() => { const tick = () => { callback(); };

if (delay !== null) {
  intervalIdRef.current = setInterval(tick, delay);
}

const id = intervalIdRef.current;
return () => {
  clearInterval(id);
};

}, [delay]);

// handle unmount useEffect(() => { const id = intervalIdRef.current; return () => { clearInterval(id); }; }, []); };

And then you can use it like this

const [counter, setCounter] = useState[0]
const onTimerFinish = useCallback(() => {
  setCounter(counter + 1)
  // setCounter will reset the interval
}, [counter])

useResetInterval(() => {
  onTimerFinish()
}, 5000)
Pun answered 3/11, 2021 at 19:36 Comment(0)
U
0

The hook is based on the useEffect => returned cleanup function React page cycle to establish and clear timers, which means that you can let this hook handle it by changing state, specifically the dependency [delay], to induce it to clear and recreate. Setting delay to null does cancel the timer, but how to restart? One easy way is just to change it by one millisecond and use the hook as is:

const DEFAULT_DELAY = 5000
const [delay, setDelay] = useState(DEFAULT_DELAY)

function useInterval(callback, delay) {
    
    const savedCallback = useRef();

    // Remember the latest callback.
    useEffect(() => {
    savedCallback.current = callback;
    }, [callback]);

    // Set up the interval.
    useEffect(() => {
    function tick() {
        savedCallback.current();
    }
    if (delay !== null) {
        let id = setInterval(tick, delay);
        return () => clearInterval(id);
    }
    }, [delay]);
}

useInterval(yourFunction, delay)

return(
    <button onClick={(ev) => { setDelay(delay===DEFAULT_DELAY?DEFAULT_DELAY+1:DEFAULT_DELAY);  handleButtonPress(ev);  }}>click me</button> 
)
Ulyanovsk answered 16/5, 2023 at 0:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.