Overview
useHoveringUsers
identifies which users are currently hovering over a specific DOM element in your collaborative application. This hook is ideal for creating interactive feedback, hover highlights, and real-time user interaction awareness.
Perfect for: Interactive elements, hover effects, collaborative drawing tools, real-time feedback systems, and any feature where you need to show user interaction with specific elements.
If a user is hovering a component that is nested within other hoverable components, only the innermost component will indicate that it’s being hovered.
Basic Usage
import { useHoveringUsers } from 'react-together'
function HoverableCard() {
const [ref, hoveringUsers, isHovering] = useHoveringUsers('card-hover')
return (
<div>
<div
ref={ref}
className={`p-4 border rounded transition-colors ${
isHovering ? 'bg-blue-100' : 'bg-white'
}`}
>
{isHovering ? "You're hovering me!" : 'Hover me!'}
{hoveringUsers.length > 0 && (
<div className="mt-2 text-sm">
Hovering users: {hoveringUsers.length}
</div>
)}
</div>
<div className="mt-2">
<h4>Users currently hovering:</h4>
<ul>
{hoveringUsers.map((userId) => (
<li key={userId}>{userId}</li>
))}
</ul>
</div>
</div>
)
}
Signature
useHoveringUsers(rtKey: string): [
ref: MutableRefObject<HTMLElement | null>,
hoveringUsers: string[],
isHovering: boolean
]
Parameters
Unique identifier for this hovering state. Must be unique across your application.
Return Value
The hook returns a tuple with three elements:
[0] ref
MutableRefObject<HTMLElement | null>
A React ref that must be attached to the DOM element you want to track hovering for
Array containing all user IDs that are currently hovering over the element
Whether the current user is hovering over the element
Examples
import { useHoveringUsers, useConnectedUsers } from 'react-together'
function InteractiveButton() {
const [ref, hoveringUsers, isHovering] = useHoveringUsers('interactive-button')
const connectedUsers = useConnectedUsers()
const getHoveringUserNames = () => {
return hoveringUsers
.map(userId => connectedUsers.find(u => u.userId === userId)?.nickname)
.filter(Boolean)
}
return (
<div className="text-center">
<button
ref={ref}
className={`px-6 py-3 rounded-lg font-semibold transition-all ${
isHovering
? 'bg-blue-600 text-white transform scale-105'
: 'bg-blue-500 text-white hover:bg-blue-600'
}`}
>
{isHovering ? 'You\'re hovering!' : 'Hover me!'}
</button>
{hoveringUsers.length > 0 && (
<div className="mt-3 text-sm text-gray-600">
<p>{hoveringUsers.length} user(s) hovering:</p>
<p className="font-medium">
{getHoveringUserNames().join(', ')}
</p>
</div>
)}
</div>
)
}
Collaborative Drawing Canvas
import { useHoveringUsers, useStateTogether } from 'react-together'
import { useState } from 'react'
interface DrawingPoint {
x: number
y: number
userId: string
timestamp: number
}
function CollaborativeCanvas() {
const [canvasRef, hoveringUsers, isHovering] = useHoveringUsers('canvas-hover')
const [drawingPoints, setDrawingPoints] = useStateTogether<DrawingPoint[]>('drawing-points', [])
const [isDrawing, setIsDrawing] = useState(false)
const handleMouseDown = (e: React.MouseEvent) => {
if (isHovering) {
setIsDrawing(true)
const rect = canvasRef.current?.getBoundingClientRect()
if (rect) {
const point: DrawingPoint = {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
userId: 'current-user', // You'd get this from your auth system
timestamp: Date.now()
}
setDrawingPoints(prev => [...prev, point])
}
}
}
const handleMouseMove = (e: React.MouseEvent) => {
if (isDrawing && canvasRef.current) {
const rect = canvasRef.current.getBoundingClientRect()
const point: DrawingPoint = {
x: e.clientX - rect.left,
y: e.clientY - rect.top,
userId: 'current-user',
timestamp: Date.now()
}
setDrawingPoints(prev => [...prev, point])
}
}
const handleMouseUp = () => {
setIsDrawing(false)
}
return (
<div className="space-y-4">
<div className="relative">
<svg
ref={canvasRef}
className={`border-2 bg-white cursor-crosshair ${
isHovering ? 'border-blue-500' : 'border-gray-300'
}`}
width={600}
height={400}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseLeave={handleMouseUp}
>
{drawingPoints.map((point, index) => (
<circle
key={index}
cx={point.x}
cy={point.y}
r={2}
fill="#3b82f6"
/>
))}
</svg>
{hoveringUsers.length > 0 && (
<div className="absolute top-2 right-2 bg-black bg-opacity-75 text-white px-2 py-1 rounded text-sm">
{hoveringUsers.length} user(s) active
</div>
)}
</div>
<div className="text-sm text-gray-600">
<p>Status: {isHovering ? 'Ready to draw' : 'Hover over canvas to draw'}</p>
<p>Points drawn: {drawingPoints.length}</p>
</div>
</div>
)
}
Hover Zones with Visual Feedback
import { useHoveringUsers } from 'react-together'
function HoverZones() {
const [zone1Ref, zone1Users, zone1Hovering] = useHoveringUsers('zone-1')
const [zone2Ref, zone2Users, zone2Hovering] = useHoveringUsers('zone-2')
const [zone3Ref, zone3Users, zone3Hovering] = useHoveringUsers('zone-3')
const zones = [
{ ref: zone1Ref, users: zone1Users, hovering: zone1Hovering, color: 'red', name: 'Zone 1' },
{ ref: zone2Ref, users: zone2Users, hovering: zone2Hovering, color: 'green', name: 'Zone 2' },
{ ref: zone3Ref, users: zone3Users, hovering: zone3Hovering, color: 'blue', name: 'Zone 3' },
]
return (
<div className="space-y-4">
<h3 className="text-lg font-semibold">Interactive Hover Zones</h3>
<div className="grid grid-cols-3 gap-4">
{zones.map(({ ref, users, hovering, color, name }) => (
<div
key={name}
ref={ref}
className={`h-32 border-2 rounded-lg flex flex-col items-center justify-center transition-all cursor-pointer ${
hovering
? `bg-${color}-100 border-${color}-500 scale-105`
: `border-${color}-300 hover:border-${color}-400`
}`}
>
<h4 className="font-medium">{name}</h4>
<p className="text-sm text-gray-600">
{users.length} user(s) hovering
</p>
{hovering && (
<p className="text-xs font-semibold text-green-600 mt-1">
You're here!
</p>
)}
</div>
))}
</div>
<div className="bg-gray-50 p-4 rounded">
<h4 className="font-medium mb-2">Activity Summary:</h4>
<ul className="space-y-1 text-sm">
{zones.map(({ users, name, color }) => (
<li key={name}>
<span className={`inline-block w-3 h-3 bg-${color}-500 rounded mr-2`}></span>
{name}: {users.length} user(s)
</li>
))}
</ul>
</div>
</div>
)
}
Nested Hover Detection
import { useHoveringUsers } from 'react-together'
function NestedHoverExample() {
const [outerRef, outerUsers, outerHovering] = useHoveringUsers('outer-container')
const [innerRef, innerUsers, innerHovering] = useHoveringUsers('inner-container')
return (
<div className="space-y-4">
<h3>Nested Hover Detection</h3>
<p className="text-sm text-gray-600">
Only the innermost element will register as being hovered when elements are nested.
</p>
<div
ref={outerRef}
className={`p-8 border-2 rounded-lg transition-colors ${
outerHovering ? 'bg-blue-50 border-blue-300' : 'border-gray-300'
}`}
>
<h4>Outer Container</h4>
<p className="text-sm mb-4">
Hovering: {outerHovering ? 'Yes' : 'No'} | Users: {outerUsers.length}
</p>
<div
ref={innerRef}
className={`p-4 border-2 rounded transition-colors ${
innerHovering ? 'bg-green-50 border-green-300' : 'border-gray-300'
}`}
>
<h5>Inner Container</h5>
<p className="text-sm">
Hovering: {innerHovering ? 'Yes' : 'No'} | Users: {innerUsers.length}
</p>
</div>
</div>
<div className="text-sm bg-yellow-50 p-3 rounded border border-yellow-200">
<strong>Try this:</strong> Hover over the outer container, then move to the inner container.
Notice how only the innermost container shows as being hovered.
</div>
</div>
)
}
Best Practices
- Unique Keys: Always use unique
rtKey
values across your application to avoid conflicts
- Cleanup: The hook automatically handles cleanup when components unmount
- Ref Assignment: Make sure to assign the returned ref to a DOM element, not a React component
User Experience
- Visual Feedback: Provide clear visual feedback when users are hovering
- Accessibility: Consider keyboard navigation and screen reader compatibility
- Performance: Be mindful of complex hover interactions that might impact performance
Common Patterns
// ✅ Good: Unique keys
const [ref1] = useHoveringUsers('unique-key-1')
const [ref2] = useHoveringUsers('unique-key-2')
// ❌ Bad: Duplicate keys
const [ref1] = useHoveringUsers('same-key')
const [ref2] = useHoveringUsers('same-key')
// ✅ Good: Attach to DOM element
<div ref={ref}>Content</div>
// ❌ Bad: Attach to React component
<MyComponent ref={ref} />
TypeScript Support
The hook is fully typed and will provide proper TypeScript intellisense:
const [
ref, // MutableRefObject<HTMLElement | null>
userIds, // string[]
isHovering // boolean
] = useHoveringUsers('my-key')