codedsaif-react-hooks
Version:
To make it easy for you to get started with GitLab, here's a list of recommended next steps.
170 lines (158 loc) • 5.7 kB
JavaScript
import { useEffect, useRef, useState } from 'react';
/**
* useInterval
* Runs a callback at a specified interval with support for pausing, debugging, initial delay, and external control.
*
* @param {Function} callback - Function to run at each interval tick.
* @param {number|null} delay - Interval duration in ms. Use `null` to pause.
* @param {Object} [options={}] - Additional options.
* @param {boolean} [options.debug=false] - Log lifecycle events to the console.
* @param {boolean} [options.stop=false] - Stops interval immediately when set to true.
* @param {boolean} [options.immediate=false] - Fires callback immediately on mount.
* @param {number} [options.initialDelay] - Wait this long before starting interval loop.
* @param {number} [options.maxTicks] - Stop after this many ticks.
*
* @returns {{
* count: number,
* reset: () => void,
* forceTick: () => void,
* pause: () => void,
* resume: () => void,
* }}
*
* @example
* // Logs "Tick" every second
* useInterval(() => console.log('Tick'), 1000);
*
* @example
* const { count, reset } = useInterval(() => {
* console.log('Tick!');
* }, 1000);
*
* @example
* // With pause/resume capability
* const [delay, setDelay] = useState(1000);
* <button onClick={() => setDelay(prev => prev ? null : 1000)}>Toggle</button>
* useInterval(() => console.log('Toggling...'), delay);
*
* @example
* function CountDown({ initialCount, downLimit, initialDelay = 1000, bgColor = 'transparent' }) {
* const [count, setCount] = useState(initialCount);
* const [delay, setDelay] = useState(null);
* useInterval(() => {
* const newValue = count - 1;
* setCount(newValue)
* if (newValue <= downLimit) setDelay(null);
* }, delay);
* return (
* <div style={{ display: 'flex', backgroundColor: bgColor }}>{count}
* <button onClick={() => setDelay(initialDelay)}>Start</button>
* <button onClick={() => setDelay(null)}>Stop</button>
* </div>
* )
* }
*
* @example
* import React, { useState, useCallback } from 'react';
* import useInterval from './useInterval';
* const ShoppingList = () => {
* // Wait 5 seconds before fetching new data
* const POLL_DELAY = 5000;
* const [items, setItems] = useState([]);
* const fetchItems = useCallback(async () => {
* const response = await fetch('/shopping-list/items');
* const json = await response.json();
* setItems(json);
* }, []);
* useEffect(() => {
* // Fetch items from API on mount
* fetchItems();
* }, []);
* useInterval(() => {
* fetchItems();
* }, POLL_DELAY);
* return (
* <ul>
* {items.map((item) => <li>{item.title}</li>)}
* </ul>
* )
* };
*/
const useInterval = (callback, delay, options) => {
const {
stop = false,
debug = false,
immediate = false,
initialDelay,
maxTicks,
} = options || {};
const savedCallback = useRef();
const intervalId = useRef(null);
const initialTimeoutId = useRef(null);
const [tickCount, setTickCount] = useState(0);
const tick = () => {
if (debug) console.log('[⏱️ useInterval] Tick', tickCount + 1);
setTickCount((prev) => prev + 1);
savedCallback.current?.();
};
const clear = () => {
if (intervalId.current) {
clearInterval(intervalId.current);
intervalId.current = null;
if (debug) console.log('[🧹 useInterval] Interval cleared');
}
if (initialTimeoutId.current) {
clearTimeout(initialTimeoutId.current);
initialTimeoutId.current = null;
if (debug) console.log('[🧼 useInterval] Initial delay timeout cleared');
}
};
const start = () => {
if (intervalId.current || delay == null || stop) return;
const runInterval = () => {
intervalId.current = setInterval(tick, delay);
if (debug) console.log('[🚀 useInterval] Interval started:', intervalId.current);
};
if (initialDelay != null) {
if (debug) console.log('[⏳ useInterval] Waiting initialDelay:', initialDelay);
initialTimeoutId.current = setTimeout(() => {
runInterval();
}, initialDelay);
} else {
runInterval();
}
};
useEffect(() => {
savedCallback.current = callback;
if (debug) console.log('[💾 useInterval] Callback updated');
}, [callback]);
useEffect(() => {
if (stop || delay === null || delay === undefined) {
clear();
return;
}
start();
return clear;
}, [delay, debug, stop, initialDelay]);
useEffect(() => {
if (maxTicks != null && tickCount >= maxTicks) {
clear();
if (debug) console.log('[✅ useInterval] Max ticks reached');
}
}, [tickCount]);
useEffect(() => {
if (immediate && typeof savedCallback.current === 'function') {
savedCallback.current();
setTickCount((prev) => prev + 1);
if (debug) console.log('[⚡ useInterval] Immediate tick fired');
}
}, []);
return {
count: tickCount,
reset: () => setTickCount(0),
forceTick: () => tick(),
pause: clear,
resume: start,
};
};
export default useInterval;