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

rtKey
string
required
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
[1] hoveringUsers
string[]
Array containing all user IDs that are currently hovering over the element
[2] isHovering
boolean
Whether the current user is hovering over the element

Examples

Interactive Button with User Feedback

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

Performance Considerations

  • 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')