Hone logo
Hone
Problems

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, and touchend events.
  • 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 the swipeDirection to 'none'.
  • Element Attachment: The hook should accept a ref to 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 minDistance threshold should be a non-negative number (in pixels).
  • The sensitivity threshold 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 touchmove event will be crucial for tracking the current position and calculating displacement.
  • The touchend event should be used to finalize the swipe detection and update the state.
  • The sensitivity parameter 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 resetSwipe function is important for managing the swipe state externally, allowing components to react and then clear the swipe direction.
Loading editor...
typescript