Hone logo
Hone
Problems

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, and direction as 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:

  1. When a user touches down on the element referenced by the hook, isTouching should become true, and startX, startY should be recorded.
  2. As the user drags their finger, currentX, currentY should update, and consequently, deltaX, deltaY, and direction should be calculated and updated.
  3. When the user lifts their finger or the touch is cancelled, isTouching should become false, and direction should 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 (touchcancel event).
  • 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:
    • isTouching will be true.
    • deltaX will be approximately 50.
    • deltaY will be approximately 20.
    • direction will likely be 'right' (depending on the threshold for directional change).
  • After lifting finger:
    • isTouching will be false.
    • deltaX, deltaY will reset to 0.
    • direction will 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 direction should be determined based on the greater of deltaX or deltaY once 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 useRef within 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 useEffect hook to prevent memory leaks.
  • Think about how you will determine the swipe direction. A common approach is to compare deltaX and deltaY after a certain threshold of movement.
  • The touchcancel event should be handled similarly to touchend.
Loading editor...
typescript