Implement useLongPress React Hook
Create a custom React hook, useLongPress, that allows developers to easily add long press functionality to any DOM element. This hook should provide a declarative way to handle the start and end of a long press, offering a more intuitive user experience for actions that require more than a simple click.
Problem Description
The goal is to build a reusable React hook called useLongPress that abstracts the logic for detecting a long press event. A long press is typically defined as holding down a mouse button or touching the screen for a specific duration.
Your useLongPress hook should:
- Accept a callback function: This function will be executed when the long press is successfully detected.
- Accept an optional
msparameter: This parameter specifies the duration in milliseconds required to trigger the long press. The default value should be 500ms. - Return props: The hook should return an object containing event handlers that can be spread onto a DOM element (e.g., a
div,button). These handlers will manage the touch and mouse events necessary to detect the long press. - Handle both mouse and touch events: The hook needs to work seamlessly across different input devices.
- Prevent default behavior where appropriate: For instance, on touch devices, the default browser behavior for a long press (like opening context menus) might need to be prevented.
- Clean up timers: Ensure that any timers are cleared when the component unmounts or when the press is released prematurely.
Examples
Example 1: Basic Usage with Mouse
Scenario: A button that triggers an alert after being held down for 1 second.
// Assume this is inside a React component:
const MyComponent = () => {
const { longPressProps } = useLongPress({
onLongPress: () => {
alert('Button long pressed!');
},
ms: 1000,
});
return (
<button {...longPressProps}>
Hold me for 1 second
</button>
);
};
Input: User holds down the mouse button on the "Hold me for 1 second" button for exactly 1000ms or more. Output: An alert box appears with the message "Button long pressed!".
Explanation: The useLongPress hook is configured with a callback and a duration. When the mouse button is pressed and held for the specified duration without being released, the onLongPress callback is invoked.
Example 2: Handling Touch Events and Preventing Default
Scenario: A draggable element that should not trigger the browser's context menu on long press.
// Assume this is inside a React component:
const DraggableItem = () => {
const { longPressProps } = useLongPress({
onLongPress: () => {
console.log('Item is being dragged (simulated)!');
// Logic to initiate drag operation would go here
},
ms: 700, // 0.7 seconds
});
return (
<div
{...longPressProps}
style={{
width: '100px',
height: '100px',
backgroundColor: 'lightblue',
userSelect: 'none', // Prevent text selection interfering
}}
>
Drag me
</div>
);
};
Input: User touches and holds the "Drag me" div for 700ms or more on a touch device. Output: The message "Item is being dragged (simulated)!" is logged to the console. The browser's default context menu (which usually appears on long press on touch devices) is not shown.
Explanation: The hook correctly handles touch events (onTouchStart, onTouchEnd, onTouchCancel). By returning preventDefault() for relevant touch events, it stops the browser from showing its default context menu. The onLongPress callback is executed after 700ms.
Example 3: Handling Premature Release
Scenario: A button that triggers an action only if held for a minimum duration. If released before that, nothing happens.
// Assume this is inside a React component:
const ConditionalButton = () => {
const { longPressProps } = useLongPress({
onLongPress: () => {
alert('Long press confirmed!');
},
ms: 600,
});
return (
<button {...longPressProps}>
Hold for 600ms
</button>
);
};
Input 1: User holds down the button for 400ms and then releases. Output 1: No alert is shown.
Input 2: User holds down the button for 700ms and then releases. Output 2: An alert box appears with the message "Long press confirmed!".
Explanation: The hook correctly manages the timer. If the press ends before the ms duration, the timer is cleared, and the onLongPress callback is not fired. If the press duration exceeds ms, the callback is executed.
Constraints
- The
useLongPresshook must be implemented in TypeScript. - The
onLongPresscallback should be invoked only once per long press sequence, even if the press duration significantly exceeds themsthreshold. - The hook should gracefully handle rapid clicks (short presses) without triggering the long press.
- The timer mechanism used should be efficient and properly managed.
- The returned event handlers should be designed to be easily spreadable onto DOM elements (
{...longPressProps}).
Notes
- Consider the differences between mouse events (
mousedown,mouseup,mouseleave) and touch events (touchstart,touchend,touchcancel). - You'll likely need to manage state within the hook to track whether a press has started, the start time, and whether the long press has already fired.
- Think about how to handle cases where the user drags their finger/mouse off the element after starting a press.
mouseleaveandtouchcancelare important here. - The
preventDefault()method is crucial for touch events to override default browser behavior.