From 73a9bff91aa7ccf5d40f13d4ddf3b9a81215bd9b Mon Sep 17 00:00:00 2001 From: reussio <84527857+reussio@users.noreply.github.com> Date: Tue, 4 Nov 2025 20:02:26 +0100 Subject: [PATCH] feat: add new useIdle hook --- .changeset/icy-impalas-try.md | 5 ++++ src/index.ts | 1 + src/useIdle/index.ts | 2 ++ src/useIdle/types.ts | 10 +++++++ src/useIdle/useIdle.ts | 52 +++++++++++++++++++++++++++++++++++ 5 files changed, 70 insertions(+) create mode 100644 .changeset/icy-impalas-try.md create mode 100644 src/useIdle/index.ts create mode 100644 src/useIdle/types.ts create mode 100644 src/useIdle/useIdle.ts diff --git a/.changeset/icy-impalas-try.md b/.changeset/icy-impalas-try.md new file mode 100644 index 0000000..9cc16cd --- /dev/null +++ b/.changeset/icy-impalas-try.md @@ -0,0 +1,5 @@ +--- +"tiny-hooks": minor +--- + +Add new `useIdle` hook diff --git a/src/index.ts b/src/index.ts index 387ceaa..8ce0bde 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export { useCounter } from "./useCounter"; export { useDebounce } from "./useDebounce"; export { useEventListener } from "./useEventListener"; export { useHover } from "./useHover"; +export { useIdle } from "./useIdle"; export { useIsClient } from "./useIsClient"; export { useIsMounted } from "./useIsMounted"; export { useLocalStorage } from "./useLocalStorage"; diff --git a/src/useIdle/index.ts b/src/useIdle/index.ts new file mode 100644 index 0000000..aaa5909 --- /dev/null +++ b/src/useIdle/index.ts @@ -0,0 +1,2 @@ +export type { UseIdleOptions, UseIdleReturn } from "./types"; +export { useIdle } from "./useIdle"; diff --git a/src/useIdle/types.ts b/src/useIdle/types.ts new file mode 100644 index 0000000..c486647 --- /dev/null +++ b/src/useIdle/types.ts @@ -0,0 +1,10 @@ +export interface UseIdleOptions { + timeout?: number; + onIdle?: () => void; + events?: string[]; +} + +export interface UseIdleReturn { + isIdle: boolean; + idleTime: number; +} diff --git a/src/useIdle/useIdle.ts b/src/useIdle/useIdle.ts new file mode 100644 index 0000000..ee2ab55 --- /dev/null +++ b/src/useIdle/useIdle.ts @@ -0,0 +1,52 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import type { UseIdleOptions, UseIdleReturn } from "./types.ts"; + +const defaultEvents = [ + "mousemove", + "mousedown", + "keydown", + "scroll", + "touchstart", + "touchmove", +]; + +export function useIdle({ + timeout = 300_000, // 5 minutes + onIdle, + events = defaultEvents, +}: UseIdleOptions = {}): UseIdleReturn { + const [isIdle, setIsIdle] = useState(false); + const [lastActive, setLastActive] = useState(Date.now()); + const timer = useRef(null); + + const resetTimer = useCallback(() => { + if (timer.current) clearTimeout(timer.current); + + setIsIdle(false); + setLastActive(Date.now()); + + timer.current = window.setTimeout(() => { + setIsIdle(true); + onIdle?.(); + }, timeout); + }, [timeout, onIdle]); + + useEffect(() => { + events.forEach((event) => { + window.addEventListener(event, resetTimer, true); + }); + + resetTimer(); + + return () => { + if (timer.current) clearTimeout(timer.current); + events.forEach((event) => { + window.removeEventListener(event, resetTimer, true); + }); + }; + }, [resetTimer, events]); + + const idleTime = Date.now() - lastActive; + + return { isIdle, idleTime }; +}