I recently needed a UI element that appears after user interaction and hides itself automatically after a few seconds. Instead of repeating this logic everywhere, I decided to extract it into a custom hook.
Here’s how I approached it.
What problem are we solving?
We want:
- A UI element that can be shown manually
- It should auto-hide after a delay
- Cleanup timers properly
- Avoid unnecessary re-renders
The Idea
Instead of managing:
setTimeout- visibility state
- cleanup logic
inside components, we move everything into a reusable hook.
The Hook
import { useState, useEffect, useRef, useCallback } from "react";
export function useAutoHide(delay = 3000) {
const [isVisible, setIsVisible] = useState(false);
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const hasUserInteracted = useRef(false);
const show = useCallback(() => {
setIsVisible(true);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
timeoutRef.current = setTimeout(() => {
setIsVisible(false);
}, delay);
}, [delay]);
const hide = useCallback(() => {
setIsVisible(false);
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
}, []);
useEffect(() => {
const handleGlobalClick = () => {
if (!hasUserInteracted.current) {
show();
hasUserInteracted.current = true;
}
};
document.addEventListener("click", handleGlobalClick, { once: true });
return () => document.removeEventListener("click", handleGlobalClick);
}, [show]);
useEffect(() => {
return () => {
if (timeoutRef.current) clearTimeout(timeoutRef.current);
};
}, []);
return { isVisible, show, hide };
}
