Implement a useTouch React Hook for Gesture Detection
In modern web applications, interactive gestures like tapping, swiping, and pinching are crucial for a smooth and intuitive user experience, especially on touch devices. This challenge asks you to create a custom React hook, useTouch, that simplifies the process of detecting and responding to these common touch gestures.
Problem Description
Your task is to implement a React hook named useTouch that listens for touch events on an HTML element and provides a convenient interface to track touch state and detect common gestures. The hook should abstract away the complexities of managing touch event listeners and state updates.
Key Requirements:
- State Management: The hook should maintain and expose the current touch state, including:
isTouching: A boolean indicating whether a touch is currently active on the element.startX,startY: The initial coordinates of the touch event.currentX,currentY: The current coordinates of the touch event.deltaX,deltaY: The difference between the current and start coordinates.direction: A string indicating the primary direction of the swipe ('left', 'right', 'up', 'down', or 'none'). This should be updated as the touch moves.
- Event Handling: The hook must attach and detach appropriate touch event listeners (
touchstart,touchmove,touchend,touchcancel) to the provided DOM element. - Gesture Detection (Basic): While full gesture recognition is complex, the hook should at least calculate and update
deltaX,deltaY, anddirectionas the touch progresses. - Return Value: The hook should return an object containing the touch state variables described above.
- Element Reference: The hook should accept a React ref object that will be attached to the target DOM element.
Expected Behavior:
- When a user touches down on the element referenced by the hook,
isTouchingshould becometrue, andstartX,startYshould be recorded. - As the user drags their finger,
currentX,currentYshould update, and consequently,deltaX,deltaY, anddirectionshould be calculated and updated. - When the user lifts their finger or the touch is cancelled,
isTouchingshould becomefalse, anddirectionshould reset to 'none'.
Edge Cases to Consider:
- Multiple touch points (though for this basic implementation, you can focus on the first touch point).
- Touch cancellation (
touchcancelevent). - Ensuring event listeners are properly cleaned up when the component unmounts.
Examples
Example 1: Basic Touch Detection
import React, { useRef } from 'react';
import useTouch from './useTouch'; // Assuming your hook is in './useTouch'
function DraggableBox() {
const boxRef = useRef<HTMLDivElement>(null);
const touchState = useTouch(boxRef);
return (
<div
ref={boxRef}
style={{
width: '100px',
height: '100px',
backgroundColor: touchState.isTouching ? 'lightblue' : 'gray',
cursor: 'grab',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
{touchState.isTouching ? 'Touching!' : 'Drag me'}
<div>
<p>Delta X: {touchState.deltaX.toFixed(2)}</p>
<p>Delta Y: {touchState.deltaY.toFixed(2)}</p>
<p>Direction: {touchState.direction}</p>
</div>
</div>
);
}
Input: User touches down on the div, drags their finger 50px to the right and 20px down, then lifts their finger.
Output (inside the DraggableBox component):
- During the drag:
isTouchingwill betrue.deltaXwill be approximately50.deltaYwill be approximately20.directionwill likely be 'right' (depending on the threshold for directional change).
- After lifting finger:
isTouchingwill befalse.deltaX,deltaYwill reset to0.directionwill reset to 'none'.
Explanation: The useTouch hook attaches listeners to the div. When the user touches, isTouching becomes true, and coordinates are stored. As they move, deltas and direction are calculated. When they release, the state resets.
Example 2: Directional Swiping Logic
Let's consider a scenario where you want to trigger an action based on swipe direction. The direction property is key here.
import React, { useRef } from 'react';
import useTouch from './useTouch';
function SwipeDetector() {
const detectorRef = useRef<HTMLDivElement>(null);
const touchState = useTouch(detectorRef);
// A simple way to infer a swipe action
const handleSwipe = () => {
if (Math.abs(touchState.deltaX) > 50 && touchState.direction === 'right') {
alert('Swiped Right!');
} else if (Math.abs(touchState.deltaX) > 50 && touchState.direction === 'left') {
alert('Swiped Left!');
}
// Add similar logic for up/down if needed
};
// In a real app, you'd likely use useEffect with dependencies,
// but for demonstration, we'll call it directly on touch end for simplicity.
// IMPORTANT: This is a simplified example. A real implementation would use React hooks correctly.
React.useEffect(() => {
if (!touchState.isTouching && (touchState.deltaX !== 0 || touchState.deltaY !== 0)) {
handleSwipe();
}
}, [touchState.deltaX, touchState.deltaY, touchState.direction, touchState.isTouching]); // Added dependencies
return (
<div
ref={detectorRef}
style={{
width: '200px',
height: '100px',
backgroundColor: '#f0f0f0',
border: '1px solid black',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
userSelect: 'none', // Prevent text selection during drag
}}
>
<p>Swipe horizontally on me!</p>
</div>
);
}
Input: User touches down on the div, drags their finger 100px to the right, then lifts their finger.
Output: An alert box appears saying "Swiped Right!".
Explanation: The useTouch hook tracks the movement. When the touch ends and the deltaX is significant and the direction is 'right', the handleSwipe logic (which would ideally be managed more robustly with useEffect) is triggered, showing the alert.
Constraints
- The hook must be implemented in TypeScript.
- The hook should focus on the primary touch point (
event.touches[0]) for simplicity. - The
directionshould be determined based on the greater ofdeltaXordeltaYonce a minimum movement threshold is crossed (e.g., 5 pixels). - The hook should not perform any DOM manipulation itself, beyond attaching event listeners. It should only expose state.
Notes
- Consider using
useRefwithin your hook to store the initial touch coordinates so they are not lost between renders. - Remember to handle the cleanup of event listeners in a
useEffecthook to prevent memory leaks. - Think about how you will determine the swipe direction. A common approach is to compare
deltaXanddeltaYafter a certain threshold of movement. - The
touchcancelevent should be handled similarly totouchend.