React Component – Calling a function when a touch occurs but ignoring swipes

I’ve written a react component that is supposed to distinguish between touches and swipes. The idea is:

  1. If the user swipes on the component, its image changes. This is working fine.
  2. If the user touches the component, it shrinks whilst being touched and expands when released. This is also working fine.
  3. It must only call “onTouch” at the end of a touch gesture, but NEVER when a swipe takes place. This is because I only want the user go the new page when they have pressed the element, not swiped across it. Think of Airbnb and when the user is swiping along an Explore card vs selecting it. This is the behaviour I want.

I can’t get Requirement 3 to work, because it is always calling onTouch when a swipe occurs. This occurs when holding, and then swiping whilst being held, or if a quick swipe takes place. But neither of these should trigger onTouch. Please advise how I can fix this? I’ve tried asking ChatGPT but it fails every time!

import React, { useRef, useState } from "react";
import {
  Animated,
  View,
  TouchableWithoutFeedback,
  StyleSheet,
} from "react-native";

interface Props {
  children: React.ReactNode;
  duration?: number; // Optional prop to control the duration of the animation
  onTouch: () => void;
}

function ShrinkOnTouch({ children, duration, onTouch }: Props) {
  if (duration === undefined) duration = 125;

  const scale = useRef(new Animated.Value(1)).current; // Initial scale value (1)
  const [isTouched, setIsTouched] = useState(false);

  // Function to handle the shrink action
  const handleTouchStart = () => {
    setIsTouched(true);
    Animated.timing(scale, {
      toValue: 0.95, // Shrink to 90% of the original size
      duration: duration, // Set the duration of the shrinking animation
      useNativeDriver: true,
    }).start();
  };

  // Function to handle the release action (expand back to original size)
  const handleTouchEnd = () => {
    if (isTouched) {
      setIsTouched(false);
      Animated.timing(scale, {
        toValue: 1, // Expand back to original size
        duration: duration, // Set the duration of the expanding animation
        useNativeDriver: true,
      }).start();
      onTouch();
    }
  };

  // Function to handle swipe detection (expand back to original size if swipe detected)
  const handleTouchMove = (e: any) => {
    if (
      isTouched &&
      (Math.abs(e.nativeEvent.pageX) > 5 || Math.abs(e.nativeEvent.pageY) > 5)
    ) {
      // If swipe detected, expand the component back to original size
      setIsTouched(false);
      Animated.timing(scale, {
        toValue: 1,
        duration: duration, // Same duration for the swipe expansion
        useNativeDriver: true,
      }).start();
    }
  };

  return (
    <View style={styles.container}>
      <TouchableWithoutFeedback
        onPressIn={handleTouchStart}
        onPressOut={handleTouchEnd}
        onPress={handleTouchEnd}
      >
        <Animated.View
          style={[styles.animatedChild, { transform: [{ scale }] }]}
          onStartShouldSetResponder={() => true}
          onResponderMove={handleTouchMove}
        >
          {children}
        </Animated.View>
      </TouchableWithoutFeedback>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  animatedChild: {
    justifyContent: "center",
    alignItems: "center",
  },
});

export default ShrinkOnTouch;