Implement a useHover React Hook in TypeScript
This challenge asks you to create a custom React hook, useHover, that tracks whether a DOM element is currently being hovered over. This is a common pattern used for implementing UI effects like tooltips, dropdowns, or visual feedback on interactive elements.
Problem Description
Your task is to implement a reusable custom React hook called useHover that accepts a React RefObject pointing to a DOM element. The hook should return a boolean value indicating whether the mouse is currently hovering over the referenced element.
Key Requirements:
-
Hook Signature: The hook should have the following signature:
function useHover<T extends HTMLElement>(ref: React.RefObject<T>): boolean;- It accepts a
React.RefObjectthat targets an HTML element (T extends HTMLElement). - It returns a
boolean:trueif hovered,falseotherwise.
- It accepts a
-
Event Handling: The hook needs to attach and detach event listeners to the referenced DOM element.
- On
mouseenter(ormouseover), it should set the hover state totrue. - On
mouseleave(ormouseout), it should set the hover state tofalse.
- On
-
State Management: The hook should manage its internal state to keep track of the hover status.
-
Cleanup: It's crucial to clean up the event listeners when the component unmounts or the ref changes to prevent memory leaks.
Expected Behavior:
When a component uses useHover with a ref to an element, the returned boolean should accurately reflect whether the user's mouse pointer is currently over that element.
Edge Cases to Consider:
- Initial State: The hook should initialize with
false(not hovered). - Ref Changes: If the
refobject itself is re-assigned (though this is less common for DOM refs), the hook should adapt. - Element Not Found: If the
ref.currentisnull(e.g., the element hasn't mounted yet), the hook should gracefully handle this without errors.
Examples
Example 1:
Component Usage:
import React, { useRef } from 'react';
import { useHover } from './useHover'; // Assuming useHover is in this file
function MyComponent() {
const buttonRef = useRef<HTMLButtonElement>(null);
const isHovered = useHover(buttonRef);
return (
<button ref={buttonRef} style={{ padding: '10px', cursor: 'pointer' }}>
{isHovered ? 'Hovering!' : 'Hover over me'}
</button>
);
}
Expected Output (based on user interaction):
- Initial Render / Mouse not over button: The button displays "Hover over me".
- Mouse enters button area: The button text changes to "Hovering!".
- Mouse leaves button area: The button text reverts to "Hover over me".
Explanation: The useHover hook correctly detects when the mouse enters and leaves the buttonRef element and provides the isHovered boolean to control the button's text.
Example 2:
Component Usage:
import React, { useRef } from 'react';
import { useHover } from './useHover';
function DivWithTooltip() {
const divRef = useRef<HTMLDivElement>(null);
const isHovered = useHover(divRef);
return (
<div
ref={divRef}
style={{
width: '100px',
height: '50px',
backgroundColor: 'lightblue',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
cursor: 'pointer',
}}
>
{isHovered ? 'Show Tooltip' : 'My Element'}
</div>
);
}
Expected Output (based on user interaction):
- Initial Render / Mouse not over div: The div displays "My Element".
- Mouse enters div area: The div text changes to "Show Tooltip".
- Mouse leaves div area: The div text reverts to "My Element".
Explanation: Similar to Example 1, the useHover hook provides the hover state, which is used here to conditionally render text within a div.
Constraints
- The solution must be written in TypeScript.
- The hook should be a pure function that adheres to React hook rules (e.g., called at the top level of a functional component or another hook).
- Event listeners must be properly added and removed to avoid memory leaks.
- The hook should handle cases where the referenced element might not be immediately available (
ref.currentcould benull).
Notes
- Consider using
useRefanduseEffecthooks within your custom hook. - Pay close attention to the dependencies array in
useEffectto ensure correct cleanup and re-attachment of event listeners if therefitself were to change (though typically DOM refs are stable). - The event names
mouseenterandmouseleaveare generally preferred overmouseoverandmouseoutfor simple hover detection as they don't bubble in the same way and can prevent redundant event firings when hovering over child elements.