Skip to content

Instantly share code, notes, and snippets.

@codeaid
Created November 10, 2020 20:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codeaid/11b2ae365fe928763ab2a77cbfbe07f3 to your computer and use it in GitHub Desktop.
Save codeaid/11b2ae365fe928763ab2a77cbfbe07f3 to your computer and use it in GitHub Desktop.
React hook utilising setTimeout and exposing pause, resume and cancel callbacks
import { useCallback, useEffect, useRef } from 'react';
type UseTimeoutCallback = (...args: Array<any>) => void;
/**
* Set a timer, which executes a function once the timer expires. Exposes pause, resume and cancel
* callbacks to the consumers.
*
* @param {UseTimeoutCallback} fn Callback function to execute after the specified timeout
* @param {number} ms Timeout in milliseconds after which to execute the callback
*/
export const useTimeout = (fn: UseTimeoutCallback, ms: number) => {
// Indicates if timer is currently running
const isRunning = useRef(false);
// Number of milliseconds remaining before the timer runs out and callback is executed
const msRemaining = useRef(ms);
// Time when the most recent execution was requested (0 if time is not currently running)
const timeStarted = useRef(0);
// Timeout handle
const handle = useRef(0);
// Original callback function
const callback = useRef(fn);
/**
* Completely cancel the execution of the callback function
*/
const cancelTimeout = useCallback(() => {
// Ignore request if there is no active timeout to cancel
if (handle.current <= 0) {
return;
}
// Mark timer as paused and reset all internal values to avoid being able to restart it
isRunning.current = false;
msRemaining.current = 0;
timeStarted.current = 0;
clearTimeout(handle.current);
handle.current = 0;
}, []);
/**
* Resume timeout countdown
*/
const resumeTimeout = useCallback(() => {
// Ignore request if time is already running or if there is no time remaining
if (isRunning.current || msRemaining.current <= 0) {
return;
}
// Mark timer as running and record the current time for future calculations
isRunning.current = true;
timeStarted.current = Date.now();
// Schedule timeout
handle.current = setTimeout(() => {
// Clear all internal values when the timer executes
cancelTimeout();
// Invoke the callback function
callback.current && callback.current();
}, msRemaining.current);
}, [cancelTimeout]);
/**
* Pause current countdown
*/
const pauseTimeout = useCallback(() => {
// Ignore request if timer is not currently running
if (!isRunning.current) {
return;
}
// Mark timer as paused, clear last time started and reduce the number of remaining
// milliseconds by the time that passed since the last "start" call
isRunning.current = false;
msRemaining.current -= Date.now() - timeStarted.current;
timeStarted.current = 0;
// Clear timeout handle to avoid the function executing while timer is paused
clearTimeout(handle.current);
handle.current = 0;
}, []);
// Update callback function reference when a new function is passed in
useEffect(() => {
callback.current = fn;
}, [fn]);
// Start the timer on mount and cancel it on unmount
useEffect(() => {
resumeTimeout();
return cancelTimeout;
}, [cancelTimeout, resumeTimeout]);
return {
cancelTimeout,
pauseTimeout,
resumeTimeout,
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment