useHover
Track hover state with pointer events fallback. Supports delays for better UX.
Live Demo
👋
Basic Hover
Hover me
⏱️
Delayed Hover
Wait 500ms
📦
Interactive Card
Hover to see the lift effect
→
Tooltip Example
📝 Event Log
No hover events yet. Try hovering over the elements!
💡 Try This:
- Hover over the basic card for instant feedback
- Try the delayed hover - notice the 500ms wait
- Hover the interactive card to see lift animation
- Hover the button to see a tooltip with delay
Code Examples
Basic Hover
import { useHover } from "@uiblock/hooks";
function Card() {
const [ref, isHovered] = useHover();
return (
<div
ref={ref}
style={{
background: isHovered ? 'blue' : 'gray',
padding: '2rem'
}}
>
{isHovered ? 'Hovering! 🎉' : 'Hover me'}
</div>
);
}With Delays
import { useHover } from "@uiblock/hooks";
function Tooltip() {
const [ref, isHovered] = useHover({
delayEnter: 500, // Wait 500ms before showing
delayLeave: 200 // Wait 200ms before hiding
});
return (
<div>
<button ref={ref}>Hover for tooltip</button>
{isHovered && (
<div className="tooltip">
This is a tooltip!
</div>
)}
</div>
);
}Hover Card with Animation
import { useHover } from "@uiblock/hooks";
function HoverCard() {
const [ref, isHovered] = useHover({ delayEnter: 100 });
return (
<div
ref={ref}
style={{
transform: isHovered ? 'scale(1.05)' : 'scale(1)',
transition: 'transform 0.2s',
boxShadow: isHovered
? '0 10px 30px rgba(0,0,0,0.2)'
: '0 2px 8px rgba(0,0,0,0.1)'
}}
>
<h3>Card Title</h3>
<p>Card content</p>
</div>
);
}How It Works
Here's the implementation:
import { useState, useEffect, useRef } from 'react'
export function useHover(options = {}) {
const { delayEnter = 0, delayLeave = 0, usePointerEvents = true } = options
const [isHovered, setIsHovered] = useState(false)
const ref = useRef(null)
const enterTimeoutRef = useRef()
const leaveTimeoutRef = useRef()
useEffect(() => {
const element = ref.current
if (!element) return
const handleEnter = () => {
// Clear any pending leave timeout
if (leaveTimeoutRef.current) {
clearTimeout(leaveTimeoutRef.current)
}
if (delayEnter > 0) {
enterTimeoutRef.current = setTimeout(() => {
setIsHovered(true)
}, delayEnter)
} else {
setIsHovered(true)
}
}
const handleLeave = () => {
// Clear any pending enter timeout
if (enterTimeoutRef.current) {
clearTimeout(enterTimeoutRef.current)
}
if (delayLeave > 0) {
leaveTimeoutRef.current = setTimeout(() => {
setIsHovered(false)
}, delayLeave)
} else {
setIsHovered(false)
}
}
// Use pointer events if supported and enabled
if (usePointerEvents && 'onpointerenter' in element) {
element.addEventListener('pointerenter', handleEnter)
element.addEventListener('pointerleave', handleLeave)
return () => {
element.removeEventListener('pointerenter', handleEnter)
element.removeEventListener('pointerleave', handleLeave)
if (enterTimeoutRef.current) clearTimeout(enterTimeoutRef.current)
if (leaveTimeoutRef.current) clearTimeout(leaveTimeoutRef.current)
}
}
// Fallback to mouse events
element.addEventListener('mouseenter', handleEnter)
element.addEventListener('mouseleave', handleLeave)
return () => {
element.removeEventListener('mouseenter', handleEnter)
element.removeEventListener('mouseleave', handleLeave)
if (enterTimeoutRef.current) clearTimeout(enterTimeoutRef.current)
if (leaveTimeoutRef.current) clearTimeout(leaveTimeoutRef.current)
}
}, [delayEnter, delayLeave, usePointerEvents])
return [ref, isHovered]
}Key Features:
- Configurable enter and leave delays
- Pointer events with mouse events fallback
- Proper timeout cleanup to prevent memory leaks
- Returns tuple of [ref, isHovered]
API Reference
Options
delayEnter?: numberDelay in ms before hover state becomes true (default: 0)
delayLeave?: numberDelay in ms before hover state becomes false (default: 0)
usePointerEvents?: booleanUse pointer events for better reliability (default: true)
returns: [RefObject, boolean]Tuple of [ref to attach, isHovered state]
💡 Use Cases
- Hover cards and previews
- Tooltips with delay
- Interactive buttons
- Image zoom on hover
- Navigation menu highlights
- Product cards in e-commerce