@qazuor/react-hooks
Version:
A comprehensive collection of production-ready React hooks for modern web applications. Features type-safe implementations, extensive testing, and zero dependencies. Includes hooks for state management, browser APIs, user interactions, and development uti
125 lines (111 loc) • 3.91 kB
text/typescript
import { useCallback, useEffect, useRef, useState } from 'react';
type UseIdlenessOptions = {
/** Time in milliseconds after which the user is considered idle. */
timeout: number;
/** A list of browser events that reset the idle timer. */
events?: string[];
/** Whether to start monitoring immediately. Default is true. */
startImmediately?: boolean;
/** Callback function to execute when idle state changes. */
onIdleChange?: (isIdle: boolean) => void;
};
type UseIdlenessReturn = {
/** Current idle state */
isIdle: boolean;
/** Start monitoring for idleness */
start: () => void;
/** Stop monitoring for idleness */
stop: () => void;
/** Reset the idle timer */
reset: () => void;
};
/**
* useIdleness
*
* @description This hook detects whether the user is idle after a specified timeout period.
* By default, it listens to user activity events such as `mousemove`, `keydown`, `wheel`, and `touchstart`.
* Once the user is inactive for the given `timeout`, the hook returns `true`. If any of the specified events occur,
* the timer resets and the hook returns `false` again.
*
* @param {UseIdlenessOptions} options - Object containing:
* - `timeout`: The idle threshold in milliseconds.
* - `events`: (optional) Array of event names that should reset the idle timer.
* - `startImmediately`: (optional) Whether to start monitoring immediately.
* - `onIdleChange`: (optional) Callback for idle state changes.
*
* @returns {boolean} - A boolean indicating whether the user is currently idle (`true`) or active (`false`).
*
* @example
* ```ts
* const isIdle = useIdleness({ timeout: 5000 });
*
* if (isIdle) {
* // The user has been idle for 5 seconds.
* }
* ```
*/
export function useIdleness({
timeout,
events = ['mousemove', 'keydown', 'wheel', 'touchstart'],
startImmediately = true,
onIdleChange
}: UseIdlenessOptions): UseIdlenessReturn {
const [idle, setIdle] = useState(false);
const [isMonitoring, setIsMonitoring] = useState(startImmediately);
const timerId = useRef<ReturnType<typeof setTimeout> | null>(null);
const mounted = useRef(true);
const eventsRef = useRef(events);
const clearTimer = useCallback(() => {
if (timerId.current) {
clearTimeout(timerId.current);
timerId.current = null;
}
}, []);
const setIdleState = useCallback(
(newState: boolean) => {
if (mounted.current && idle !== newState) {
setIdle(newState);
onIdleChange?.(newState);
}
},
[idle, onIdleChange]
);
const resetTimer = useCallback(() => {
clearTimer();
setIdleState(false);
if (isMonitoring) {
timerId.current = setTimeout(() => {
setIdleState(true);
}, timeout);
}
}, [timeout, isMonitoring, clearTimer, setIdleState]);
const start = useCallback(() => {
setIsMonitoring(true);
resetTimer();
resetTimer();
}, [resetTimer]);
const stop = useCallback(() => {
setIsMonitoring(false);
clearTimer();
setIdleState(false);
}, [clearTimer, setIdleState]);
useEffect(() => {
if (isMonitoring) {
eventsRef.current.forEach((evt) => window.addEventListener(evt, resetTimer));
timerId.current = setTimeout(() => {
setIdleState(true);
}, timeout);
}
return () => {
clearTimer();
eventsRef.current.forEach((evt) => window.removeEventListener(evt, resetTimer));
};
}, [isMonitoring, resetTimer, clearTimer, timeout, setIdleState]);
useEffect(
() => () => {
mounted.current = false;
},
[]
);
return { isIdle: idle, start, stop, reset: resetTimer };
}