React useSwipe Custom Hook Challenge
This challenge is to create a custom React hook, useSwipe, that detects swipe gestures (left, right, up, down) on an element. This hook is invaluable for building mobile-first user interfaces, enabling intuitive navigation and interactions within web applications.
Problem Description
Your task is to implement a useSwipe custom hook in TypeScript. This hook should attach event listeners to a provided DOM element (or a ref pointing to one) to detect swipe gestures. The hook should return an object containing the current swipe direction and potentially a function to reset the swipe state.
Key Requirements:
- Detect Swipe Direction: Identify and return one of the following directions: 'left', 'right', 'up', 'down', or 'none' (if no swipe is detected or the gesture is too short/invalid).
- Event Handling: The hook should listen for
touchstart,touchmove, andtouchendevents. - Thresholds: Implement configurable thresholds for:
- Minimum Distance: A swipe should only be registered if the distance moved is greater than this minimum.
- Directional Sensitivity: The swipe direction should be determined by the axis with the greatest displacement, provided it exceeds a certain sensitivity threshold relative to the other axis.
- Return Value: The hook should return an object containing:
swipeDirection: A string representing the detected swipe direction ('left', 'right', 'up', 'down', 'none').resetSwipe: A function to manually reset theswipeDirectionto 'none'.
- Element Attachment: The hook should accept a
refto the DOM element that will be used for swipe detection.
Expected Behavior:
When a user touches an element, holds their finger down, and moves it a significant distance in a particular direction, the swipeDirection should update accordingly. Releasing the touch should ideally finalize the detected swipe (though the state might persist until reset or a new gesture begins).
Edge Cases to Consider:
- Gestures that are too short to be considered a swipe.
- Gestures that are diagonal (how to prioritize direction).
- Rapid touches without significant movement.
- Multiple touches (though for this challenge, focus on single-touch swipes).
- The element might not be present when the hook is initialized or unmounted.
Examples
Example 1: Basic Swipe Detection
// Assuming this is within a React component using the hook
const elementRef = useRef<HTMLDivElement>(null);
const { swipeDirection } = useSwipe(elementRef, { minDistance: 50, sensitivity: 0.5 });
// In the DOM: <div ref={elementRef}>...</div>
// User touches down on the div, then slides their finger 70px to the right.
// 'swipeDirection' will become 'right'.
// User then slides their finger 30px up.
// 'swipeDirection' will likely remain 'right' or revert to 'none' if the movement is not significant enough to override.
Example 2: Swipe and Reset
const elementRef = useRef<HTMLDivElement>(null);
const { swipeDirection, resetSwipe } = useSwipe(elementRef, { minDistance: 30 });
// User swipes left. 'swipeDirection' becomes 'left'.
// After some action is taken based on the swipe, the developer calls resetSwipe().
// 'swipeDirection' will reset to 'none'.
Example 3: Sensitivity and Thresholds
const elementRef = useRef<HTMLDivElement>(null);
const { swipeDirection } = useSwipe(elementRef, { minDistance: 40, sensitivity: 0.8 });
// User moves finger 50px right and 20px down.
// Horizontal movement (50px) is greater than vertical (20px).
// The ratio of horizontal to vertical movement is 50/20 = 2.5.
// Since 2.5 * (1 - sensitivity=0.8) = 2.5 * 0.2 = 0.5, which is less than the difference in movement (50-20=30),
// and 50px > minDistance, 'swipeDirection' will be 'right'.
// User moves finger 40px right and 35px down.
// Horizontal movement (40px) is greater than vertical (35px).
// The ratio of horizontal to vertical movement is 40/35 ≈ 1.14.
// The difference in movement is 40 - 35 = 5px.
// With a high sensitivity (0.8), the condition for a pure directional swipe becomes stricter.
// The horizontal displacement is dominant and exceeds minDistance, so 'swipeDirection' will be 'right'.
// (Note: The exact calculation for sensitivity can be nuanced. For this problem, interpret sensitivity as a factor that makes it harder for diagonal swipes to be classified definitively. A common approach is `abs(deltaX) > abs(deltaY) * sensitivity` or `abs(deltaY) > abs(deltaX) * sensitivity` for directional purity.)
Constraints
- The hook must be written in TypeScript.
- It should only use standard browser event APIs and React hooks (
useState,useRef,useEffect). No external libraries are allowed for the core swipe detection logic. - The
minDistancethreshold should be a non-negative number (in pixels). - The
sensitivitythreshold should be a number between 0 and 1 (inclusive). A sensitivity of 1 means a pure horizontal or vertical swipe is required, while 0 means any movement can determine direction. - The hook should clean up its event listeners when the component unmounts.
Notes
- Consider how to handle the initial touch (
touchstart) to record the starting coordinates. - The
touchmoveevent will be crucial for tracking the current position and calculating displacement. - The
touchendevent should be used to finalize the swipe detection and update the state. - The
sensitivityparameter is meant to help distinguish between pure horizontal/vertical swipes and diagonal ones. A higher sensitivity means the primary axis of movement needs to be significantly larger than the secondary axis. - You might want to store intermediate touch start coordinates and current touch coordinates within the hook's state or refs.
- The returned
resetSwipefunction is important for managing the swipe state externally, allowing components to react and then clear the swipe direction.