Introduction
Handling User input and gestureare fundamental aspects of creating engaging mobile applications. React Native provides several ways to handle user interactions, from simple touch events to complex multi-touch gestures like pinch-to-zoom and pan-and-drag operations.
This chapter will cover:
- Understanding React Native’s gesture responder system
 - Implementing basic and advanced gestures
 - Using the React Native Gesture Handler library
 - Optimizing gesture performance
 - Handling gesture conflicts
 
Basic Touch Events
React Native provides several built-in touch event handlers that you can use with most components.
onPress and onPressIn/onPressOut
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, Alert, StyleSheet } from 'react-native';
const BasicTouchExample = () => {
  const [pressCount, setPressCount] = useState(0);
  const handlePress = () => {
    setPressCount(prevCount => prevCount + 1);
    Alert.alert('Button Pressed', `Press count: ${pressCount + 1}`);
  };
  const handlePressIn = () => {
    console.log('Press started');
  };
  const handlePressOut = () => {
    console.log('Press ended');
  };
  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={styles.button}
        onPress={handlePress}
        onPressIn={handlePressIn}
        onPressOut={handlePressOut}
      >
        <Text style={styles.buttonText}>
          Press Me ({pressCount})
        </Text>
      </TouchableOpacity>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  button: {
    backgroundColor: '#007AFF',
    padding: 15,
    borderRadius: 8,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
});
export default BasicTouchExample;Different Touchable Components
import React from 'react';
import {
  View,
  Text,
  TouchableOpacity,
  TouchableHighlight,
  TouchableWithoutFeedback,
  Pressable,
  StyleSheet
} from 'react-native';
const TouchableComponentsExample = () => {
  const handlePress = (type) => {
    console.log(`${type} pressed`);
  };
  return (
    <View style={styles.container}>
      <TouchableOpacity
        style={styles.touchable}
        onPress={() => handlePress('TouchableOpacity')}
      >
        <Text style={styles.text}>TouchableOpacity</Text>
      </TouchableOpacity>
      <TouchableHighlight
        style={styles.touchable}
        underlayColor="#DDDDDD"
        onPress={() => handlePress('TouchableHighlight')}
      >
        <Text style={styles.text}>TouchableHighlight</Text>
      </TouchableHighlight>
      <TouchableWithoutFeedback
        onPress={() => handlePress('TouchableWithoutFeedback')}
      >
        <View style={styles.touchable}>
          <Text style={styles.text}>TouchableWithoutFeedback</Text>
        </View>
      </TouchableWithoutFeedback>
      <Pressable
        style={({ pressed }) => [
          styles.touchable,
          { backgroundColor: pressed ? '#DDDDDD' : '#F0F0F0' }
        ]}
        onPress={() => handlePress('Pressable')}
      >
        <Text style={styles.text}>Pressable (Recommended)</Text>
      </Pressable>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  touchable: {
    backgroundColor: '#F0F0F0',
    padding: 15,
    margin: 10,
    borderRadius: 8,
    width: 200,
    alignItems: 'center',
  },
  text: {
    fontSize: 16,
  },
});
export default TouchableComponentsExample;Gesture Responder System
React Native’s gesture responder system determines which component should handle touch events when multiple components are involved.
Basic Gesture Responder
import React, { useRef } from 'react';
import { View, Text, PanResponder, Animated, StyleSheet } from 'react-native';
const GestureResponderExample = () => {
  const pan = useRef(new Animated.ValueXY()).current;
  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: () => true,
      onPanResponderGrant: () => {
        pan.setOffset({
          x: pan.x._value,
          y: pan.y._value,
        });
      },
      onPanResponderMove: Animated.event(
        [null, { dx: pan.x, dy: pan.y }],
        { useNativeDriver: false }
      ),
      onPanResponderRelease: () => {
        pan.flattenOffset();
      },
    })
  ).current;
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Drag the blue square!</Text>
      <Animated.View
        style={[
          styles.box,
          {
            transform: [{ translateX: pan.x }, { translateY: pan.y }],
          },
        ]}
        {...panResponder.panHandlers}
      >
        <Text style={styles.boxText}>Drag me!</Text>
      </Animated.View>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 18,
    marginBottom: 20,
  },
  box: {
    height: 100,
    width: 100,
    backgroundColor: 'blue',
    borderRadius: 5,
    justifyContent: 'center',
    alignItems: 'center',
  },
  boxText: {
    color: 'white',
    fontWeight: 'bold',
  },
});
export default GestureResponderExample;Advanced Gesture Responder with Boundaries
import React, { useRef, useState } from 'react';
import {
  View,
  Text,
  PanResponder,
  Animated,
  Dimensions,
  StyleSheet
} from 'react-native';
const { width, height } = Dimensions.get('window');
const BoundedDragExample = () => {
  const pan = useRef(new Animated.ValueXY()).current;
  const [isDragging, setIsDragging] = useState(false);
  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: () => true,
      onPanResponderGrant: () => {
        setIsDragging(true);
        pan.setOffset({
          x: pan.x._value,
          y: pan.y._value,
        });
      },
      onPanResponderMove: (event, gestureState) => {
        const { dx, dy } = gestureState;
        const maxX = width - 100; // Box width
        const maxY = height - 100; // Box height
       
        const newX = Math.max(0, Math.min(maxX, dx));
        const newY = Math.max(0, Math.min(maxY, dy));
       
        pan.setValue({ x: newX, y: newY });
      },
      onPanResponderRelease: () => {
        setIsDragging(false);
        pan.flattenOffset();
      },
    })
  ).current;
  return (
    <View style={styles.container}>
      <Text style={styles.title}>
        Drag within boundaries (Status: {isDragging ? 'Dragging' : 'Idle'})
      </Text>
      <Animated.View
        style={[
          styles.box,
          {
            transform: [{ translateX: pan.x }, { translateY: pan.y }],
            backgroundColor: isDragging ? 'red' : 'blue',
          },
        ]}
        {...panResponder.panHandlers}
      >
        <Text style={styles.boxText}>Bounded Drag</Text>
      </Animated.View>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 50,
  },
  title: {
    fontSize: 16,
    textAlign: 'center',
    marginBottom: 20,
    paddingHorizontal: 20,
  },
  box: {
    height: 100,
    width: 100,
    backgroundColor: 'blue',
    borderRadius: 5,
    justifyContent: 'center',
    alignItems: 'center',
    position: 'absolute',
  },
  boxText: {
    color: 'white',
    fontWeight: 'bold',
    textAlign: 'center',
  },
});
export default BoundedDragExample;PanGestureHandler
For more complex gesture handling, especially when dealing with multiple gestures, React Native Gesture Handler provides better performance and more features.
Installation
npm install react-native-gesture-handler
# or
yarn add react-native-gesture-handlerBasic Pan Gesture
import React, { useRef } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  runOnJS,
} from 'react-native-reanimated';
const PanGestureExample = () => {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const onGestureEvent = useAnimatedGestureHandler({
    onStart: (_, context) => {
      context.startX = translateX.value;
      context.startY = translateY.value;
    },
    onActive: (event, context) => {
      translateX.value = context.startX + event.translationX;
      translateY.value = context.startY + event.translationY;
    },
    onEnd: () => {
      // Optional: Add snap-back animation
      // translateX.value = withSpring(0);
      // translateY.value = withSpring(0);
    },
  });
  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        { translateX: translateX.value },
        { translateY: translateY.value },
      ],
    };
  });
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Pan Gesture with Gesture Handler</Text>
      <PanGestureHandler onGestureEvent={onGestureEvent}>
        <Animated.View style={[styles.box, animatedStyle]}>
          <Text style={styles.boxText}>Pan Me!</Text>
        </Animated.View>
      </PanGestureHandler>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 18,
    marginBottom: 50,
  },
  box: {
    width: 100,
    height: 100,
    backgroundColor: 'purple',
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
  },
  boxText: {
    color: 'white',
    fontWeight: 'bold',
  },
});
export default PanGestureExample;Pinch and Zoom Gestures
Implementing pinch-to-zoom functionality is common in image viewers and maps.
import React, { useRef } from 'react';
import { View, Text, Image, StyleSheet } from 'react-native';
import { PinchGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
} from 'react-native-reanimated';
const PinchZoomExample = () => {
  const scale = useSharedValue(1);
  const focalX = useSharedValue(0);
  const focalY = useSharedValue(0);
  const pinchGestureHandler = useAnimatedGestureHandler({
    onStart: (event, context) => {
      context.startScale = scale.value;
    },
    onActive: (event, context) => {
      scale.value = context.startScale * event.scale;
      focalX.value = event.focalX;
      focalY.value = event.focalY;
    },
    onEnd: () => {
      if (scale.value < 1) {
        scale.value = withSpring(1);
      } else if (scale.value > 3) {
        scale.value = withSpring(3);
      }
    },
  });
  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        { scale: scale.value },
      ],
    };
  });
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Pinch to Zoom</Text>
      <PinchGestureHandler onGestureEvent={pinchGestureHandler}>
        <Animated.View style={[styles.imageContainer, animatedStyle]}>
          <View style={styles.imagePlaceholder}>
            <Text style={styles.placeholderText}>
              Pinch to zoom this content
            </Text>
          </View>
        </Animated.View>
      </PinchGestureHandler>
      <Text style={styles.instructions}>
        Use two fingers to pinch and zoom
      </Text>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 18,
    marginBottom: 20,
  },
  imageContainer: {
    width: 200,
    height: 200,
    marginBottom: 20,
  },
  imagePlaceholder: {
    flex: 1,
    backgroundColor: '#E0E0E0',
    justifyContent: 'center',
    alignItems: 'center',
    borderRadius: 10,
  },
  placeholderText: {
    textAlign: 'center',
    color: '#666',
    fontSize: 16,
  },
  instructions: {
    fontSize: 14,
    color: '#666',
    textAlign: 'center',
  },
});
export default PinchZoomExample;Swipe Gestures
Swipe gestures are useful for navigation, dismissing content, and triggering actions.
import React, { useState } from 'react';
import { View, Text, StyleSheet, Alert } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
const SwipeGestureExample = () => {
  const [swipeDirection, setSwipeDirection] = useState('');
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const detectSwipe = (translationX, translationY, velocityX, velocityY) => {
    const minSwipeDistance = 50;
    const minSwipeVelocity = 500;
    if (Math.abs(translationX) > minSwipeDistance || Math.abs(velocityX) > minSwipeVelocity) {
      if (translationX > 0) {
        setSwipeDirection('Right');
      } else {
        setSwipeDirection('Left');
      }
    } else if (Math.abs(translationY) > minSwipeDistance || Math.abs(velocityY) > minSwipeVelocity) {
      if (translationY > 0) {
        setSwipeDirection('Down');
      } else {
        setSwipeDirection('Up');
      }
    }
  };
  const gestureHandler = useAnimatedGestureHandler({
    onStart: () => {
      runOnJS(setSwipeDirection)('');
    },
    onActive: (event) => {
      translateX.value = event.translationX;
      translateY.value = event.translationY;
    },
    onEnd: (event) => {
      runOnJS(detectSwipe)(
        event.translationX,
        event.translationY,
        event.velocityX,
        event.velocityY
      );
     
      translateX.value = withSpring(0);
      translateY.value = withSpring(0);
    },
  });
  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        { translateX: translateX.value },
        { translateY: translateY.value },
      ],
    };
  });
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Swipe Gesture Detection</Text>
      <Text style={styles.direction}>
        Last swipe: {swipeDirection || 'None'}
      </Text>
     
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View style={[styles.swipeArea, animatedStyle]}>
          <Text style={styles.swipeText}>Swipe me in any direction!</Text>
        </Animated.View>
      </PanGestureHandler>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 18,
    marginBottom: 10,
  },
  direction: {
    fontSize: 16,
    marginBottom: 30,
    color: '#666',
  },
  swipeArea: {
    width: 200,
    height: 200,
    backgroundColor: '#4CAF50',
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
  },
  swipeText: {
    color: 'white',
    fontSize: 16,
    textAlign: 'center',
    fontWeight: 'bold',
  },
});
export default SwipeGestureExample;Long Press Gestures
Long press gestures are useful for context menus and secondary actions.
import React, { useState } from 'react';
import { View, Text, StyleSheet, Alert } from 'react-native';
import { LongPressGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
const LongPressExample = () => {
  const [isPressed, setIsPressed] = useState(false);
  const scale = useSharedValue(1);
  const showContextMenu = () => {
    Alert.alert(
      'Context Menu',
      'Long press detected!',
      [
        { text: 'Cancel', style: 'cancel' },
        { text: 'Delete', style: 'destructive' },
        { text: 'Edit', style: 'default' },
      ]
    );
  };
  const longPressHandler = useAnimatedGestureHandler({
    onStart: () => {
      runOnJS(setIsPressed)(true);
      scale.value = withSpring(0.9);
    },
    onActive: () => {
      // Long press is active
    },
    onEnd: () => {
      runOnJS(setIsPressed)(false);
      scale.value = withSpring(1);
    },
    onFinish: (event) => {
      if (event.state === State.ACTIVE) {
        runOnJS(showContextMenu)();
      }
    },
  });
  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ scale: scale.value }],
    };
  });
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Long Press Gesture</Text>
      <Text style={styles.subtitle}>
        Press and hold the button below
      </Text>
     
      <LongPressGestureHandler
        onGestureEvent={longPressHandler}
        minDurationMs={500}
      >
        <Animated.View style={[styles.button, animatedStyle]}>
          <Text style={styles.buttonText}>
            {isPressed ? 'Hold...' : 'Long Press Me'}
          </Text>
        </Animated.View>
      </LongPressGestureHandler>
     
      <Text style={styles.instructions}>
        Hold for 500ms to trigger context menu
      </Text>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 18,
    marginBottom: 10,
  },
  subtitle: {
    fontSize: 14,
    color: '#666',
    marginBottom: 30,
  },
  button: {
    backgroundColor: '#FF6B6B',
    paddingHorizontal: 30,
    paddingVertical: 15,
    borderRadius: 25,
    marginBottom: 20,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  instructions: {
    fontSize: 12,
    color: '#666',
    textAlign: 'center',
  },
});
export default LongPressExample;React Native Gesture Handler
React Native Gesture Handler provides better performance and more gesture types compared to the built-in PanResponder.
Simultaneous Gestures
import React, { useRef } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import {
  PanGestureHandler,
  PinchGestureHandler,
  simultaneousHandlers,
} from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
} from 'react-native-reanimated';
const SimultaneousGesturesExample = () => {
  const panRef = useRef();
  const pinchRef = useRef();
 
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const scale = useSharedValue(1);
  const panGestureHandler = useAnimatedGestureHandler({
    onStart: (_, context) => {
      context.startX = translateX.value;
      context.startY = translateY.value;
    },
    onActive: (event, context) => {
      translateX.value = context.startX + event.translationX;
      translateY.value = context.startY + event.translationY;
    },
  });
  const pinchGestureHandler = useAnimatedGestureHandler({
    onStart: (_, context) => {
      context.startScale = scale.value;
    },
    onActive: (event, context) => {
      scale.value = context.startScale * event.scale;
    },
  });
  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        { translateX: translateX.value },
        { translateY: translateY.value },
        { scale: scale.value },
      ],
    };
  });
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Simultaneous Pan & Pinch</Text>
     
      <PanGestureHandler
        ref={panRef}
        onGestureEvent={panGestureHandler}
        simultaneousHandlers={pinchRef}
      >
        <Animated.View>
          <PinchGestureHandler
            ref={pinchRef}
            onGestureEvent={pinchGestureHandler}
            simultaneousHandlers={panRef}
          >
            <Animated.View style={[styles.box, animatedStyle]}>
              <Text style={styles.boxText}>Pan & Pinch Me!</Text>
            </Animated.View>
          </PinchGestureHandler>
        </Animated.View>
      </PanGestureHandler>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  title: {
    fontSize: 18,
    marginBottom: 50,
  },
  box: {
    width: 150,
    height: 150,
    backgroundColor: '#FFA726',
    borderRadius: 15,
    justifyContent: 'center',
    alignItems: 'center',
  },
  boxText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
    textAlign: 'center',
  },
});
export default SimultaneousGesturesExample;
Custom Gesture Handling
Creating custom gesture handlers for specific use cases.
import React, { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
const CustomDrawerGesture = () => {
  const [isOpen, setIsOpen] = useState(false);
  const translateX = useSharedValue(0);
  const drawerWidth = 250;
  const toggleDrawer = () => {
    setIsOpen(!isOpen);
  };
  const gestureHandler = useAnimatedGestureHandler({
    onStart: (_, context) => {
      context.startX = translateX.value;
    },
    onActive: (event, context) => {
      const newTranslateX = context.startX + event.translationX;
     
      // Constrain the drawer movement
      if (newTranslateX >= 0 && newTranslateX <= drawerWidth) {
        translateX.value = newTranslateX;
      }
    },
    onEnd: (event) => {
      const { translationX, velocityX } = event;
      const shouldOpen = translationX > drawerWidth / 2 || velocityX > 500;
     
      if (shouldOpen) {
        translateX.value = withSpring(drawerWidth);
        runOnJS(setIsOpen)(true);
      } else {
        translateX.value = withSpring(0);
        runOnJS(setIsOpen)(false);
      }
    },
  });
  const drawerStyle = useAnimatedStyle(() => {
    return {
      transform: [{ translateX: translateX.value - drawerWidth }],
    };
  });
  const overlayStyle = useAnimatedStyle(() => {
    return {
      opacity: translateX.value / drawerWidth,
    };
  });
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Custom Drawer Gesture</Text>
      <Text style={styles.subtitle}>
        Swipe from left edge to open drawer
      </Text>
     
      {/* Overlay */}
      <Animated.View style={[styles.overlay, overlayStyle]} />
     
      {/* Drawer */}
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View style={[styles.drawer, drawerStyle]}>
          <Text style={styles.drawerTitle}>Menu</Text>
          <View style={styles.menuItem}>
            <Text style={styles.menuText}>Home</Text>
          </View>
          <View style={styles.menuItem}>
            <Text style={styles.menuText}>Profile</Text>
          </View>
          <View style={styles.menuItem}>
            <Text style={styles.menuText}>Settings</Text>
          </View>
        </Animated.View>
      </PanGestureHandler>
     
      {/* Main content */}
      <View style={styles.content}>
        <Text style={styles.contentText}>
          Main Content Area
        </Text>
        <Text style={styles.status}>
          Drawer is {isOpen ? 'Open' : 'Closed'}
        </Text>
      </View>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  title: {
    fontSize: 18,
    textAlign: 'center',
    marginTop: 50,
    marginBottom: 10,
  },
  subtitle: {
    fontSize: 14,
    textAlign: 'center',
    color: '#666',
    marginBottom: 20,
  },
  overlay: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: 'black',
    zIndex: 1,
  },
  drawer: {
    position: 'absolute',
    left: 0,
    top: 0,
    bottom: 0,
    width: 250,
    backgroundColor: 'white',
    zIndex: 2,
    paddingTop: 100,
    paddingHorizontal: 20,
    shadowColor: '#000',
    shadowOffset: { width: 2, height: 0 },
    shadowOpacity: 0.3,
    shadowRadius: 5,
    elevation: 5,
  },
  drawerTitle: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 30,
    color: '#333',
  },
  menuItem: {
    paddingVertical: 15,
    borderBottomWidth: 1,
    borderBottomColor: '#E0E0E0',
  },
  menuText: {
    fontSize: 18,
    color: '#333',
  },
  content: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  contentText: {
    fontSize: 20,
    marginBottom: 20,
  },
  status: {
    fontSize: 16,
    color: '#666',
  },
});
export default CustomDrawerGesture;Gesture Conflicts and Priority
Managing gesture conflicts when multiple gesture handlers are present.
import React, { useRef } from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
} from 'react-native-reanimated';
const GestureConflictExample = () => {
  const parentPanRef = useRef();
  const childPanRef = useRef();
 
  const parentTranslateX = useSharedValue(0);
  const childTranslateX = useSharedValue(0);
  const parentGestureHandler = useAnimatedGestureHandler({
    onStart: (_, context) => {
      context.startX = parentTranslateX.value;
    },
    onActive: (event, context) => {
      parentTranslateX.value = context.startX + event.translationX;
    },
    onEnd: () => {
      parentTranslateX.value = withSpring(0);
    },
  });
  const childGestureHandler = useAnimatedGestureHandler({
    onStart: (_, context) => {
      context.startX = childTranslateX.value;
    },
    onActive: (event, context) => {
      childTranslateX.value = context.startX + event.translationX;
    },
    onEnd: () => {
      childTranslateX.value = withSpring(0);
    },
  });
  const parentStyle = useAnimatedStyle(() => {
    return {
      transform: [{ translateX: parentTranslateX.value }],
    };
  });
  const childStyle = useAnimatedStyle(() => {
    return {
      transform: [{ translateX: childTranslateX.value }],
    };
  });
  return (
    <ScrollView style={styles.container}>
      <Text style={styles.title}>Gesture Conflicts & Priority</Text>
     
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>Parent-Child Gesture Conflict</Text>
       
        <PanGestureHandler
          ref={parentPanRef}
          onGestureEvent={parentGestureHandler}
        >
          <Animated.View style={[styles.parentBox, parentStyle]}>
            <Text style={styles.parentText}>Parent (Drag me)</Text>
           
            <PanGestureHandler
              ref={childPanRef}
              onGestureEvent={childGestureHandler}
              shouldCancelWhenOutside
            >
              <Animated.View style={[styles.childBox, childStyle]}>
                <Text style={styles.childText}>Child (Drag me too)</Text>
              </Animated.View>
            </PanGestureHandler>
          </Animated.View>
        </PanGestureHandler>
      </View>
     
      <View style={styles.explanation}>
        <Text style={styles.explanationTitle}>Explanation:</Text>
        <Text style={styles.explanationText}>
          • Child gesture handler has priority over parent
        </Text>
        <Text style={styles.explanationText}>
          • shouldCancelWhenOutside prevents parent activation
        </Text>
        <Text style={styles.explanationText}>
          • Try dragging both the parent and child areas
        </Text>
      </View>
    </ScrollView>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    textAlign: 'center',
    marginBottom: 30,
    marginTop: 40,
  },
  section: {
    marginBottom: 30,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 15,
  },
  parentBox: {
    width: 300,
    height: 200,
    backgroundColor: '#4CAF50',
    borderRadius: 10,
    padding: 20,
    alignSelf: 'center',
  },
  parentText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  childBox: {
    width: 150,
    height: 80,
    backgroundColor: '#2196F3',
    borderRadius: 8,
    justifyContent: 'center',
    alignItems: 'center',
    marginTop: 10,
  },
  childText: {
    color: 'white',
    fontSize: 14,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  explanation: {
    backgroundColor: 'white',
    padding: 20,
    borderRadius: 10,
    marginTop: 20,
  },
  explanationTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  explanationText: {
    fontSize: 14,
    marginBottom: 5,
    color: '#666',
  },
});
export default GestureConflictExample;Performance Considerations
Optimizing gesture performance for smooth user interactions.
import React, { useRef, useCallback } from 'react';
import { View, Text, StyleSheet, FlatList } from 'react-native';
import { PanGestureHandler } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
const PerformanceOptimizedGestures = () => {
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const scale = useSharedValue(1);
  // Use useCallback to prevent unnecessary re-renders
  const onGestureUpdate = useCallback((x, y) => {
    console.log(`Position: ${x.toFixed(2)}, ${y.toFixed(2)}`);
  }, []);
  // Optimized gesture handler with native driver
  const gestureHandler = useAnimatedGestureHandler({
    onStart: (_, context) => {
      context.startX = translateX.value;
      context.startY = translateY.value;
      scale.value = withSpring(1.1); // Immediate feedback
    },
    onActive: (event, context) => {
      translateX.value = context.startX + event.translationX;
      translateY.value = context.startY + event.translationY;
     
      // Throttle JS calls for performance
      if (Math.abs(event.translationX) % 10 < 1) {
        runOnJS(onGestureUpdate)(translateX.value, translateY.value);
      }
    },
    onEnd: () => {
      scale.value = withSpring(1);
      // Optional: Add boundary constraints
      // translateX.value = withSpring(0);
      // translateY.value = withSpring(0);
    },
  });
  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        { translateX: translateX.value },
        { translateY: translateY.value },
        { scale: scale.value },
      ],
    };
  }, []);
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Performance Optimized Gestures</Text>
     
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View style={[styles.draggableBox, animatedStyle]}>
          <Text style={styles.boxText}>Optimized Drag</Text>
        </Animated.View>
      </PanGestureHandler>
     
      <View style={styles.tips}>
        <Text style={styles.tipsTitle}>Performance Tips:</Text>
        <Text style={styles.tip}>• Use useNativeDriver for transforms</Text>
        <Text style={styles.tip}>• Throttle runOnJS calls</Text>
        <Text style={styles.tip}>• Use useCallback for event handlers</Text>
        <Text style={styles.tip}>• Minimize state updates during gestures</Text>
      </View>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#F5F5F5',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    textAlign: 'center',
    marginTop: 50,
    marginBottom: 50,
  },
  draggableBox: {
    width: 120,
    height: 120,
    backgroundColor: '#FF5722',
    borderRadius: 15,
    justifyContent: 'center',
    alignItems: 'center',
    alignSelf: 'center',
    marginBottom: 50,
  },
  boxText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  tips: {
    backgroundColor: 'white',
    padding: 20,
    borderRadius: 10,
  },
  tipsTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 15,
  },
  tip: {
    fontSize: 14,
    marginBottom: 8,
    color: '#666',
  },
});
export default PerformanceOptimizedGestures;Best Practices
Guidelines for implementing gestures effectively in React Native applications.
1. Gesture Feedback and Visual Cues
import React, { useState } from 'react';
import { View, Text, StyleSheet, Haptics } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  withTiming,
  runOnJS,
} from 'react-native-reanimated';
const GestureFeedbackExample = () => {
  const [gestureState, setGestureState] = useState('idle');
  const translateX = useSharedValue(0);
  const backgroundColor = useSharedValue(0);
  const borderRadius = useSharedValue(10);
  const triggerHaptic = () => {
    // Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
  };
  const gestureHandler = useAnimatedGestureHandler({
    onStart: () => {
      runOnJS(setGestureState)('active');
      runOnJS(triggerHaptic)();
      backgroundColor.value = withTiming(1);
      borderRadius.value = withSpring(20);
    },
    onActive: (event) => {
      translateX.value = event.translationX;
    },
    onEnd: () => {
      runOnJS(setGestureState)('idle');
      translateX.value = withSpring(0);
      backgroundColor.value = withTiming(0);
      borderRadius.value = withSpring(10);
    },
  });
  const animatedStyle = useAnimatedStyle(() => {
    const bgColor = backgroundColor.value === 1 ? '#4CAF50' : '#2196F3';
   
    return {
      transform: [{ translateX: translateX.value }],
      backgroundColor: bgColor,
      borderRadius: borderRadius.value,
    };
  });
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Gesture Feedback Best Practices</Text>
     
      <Text style={styles.status}>
        Status: {gestureState}
      </Text>
     
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View style={[styles.feedbackBox, animatedStyle]}>
          <Text style={styles.boxText}>Drag for Feedback</Text>
        </Animated.View>
      </PanGestureHandler>
     
      <View style={styles.practicesList}>
        <Text style={styles.practicesTitle}>Best Practices:</Text>
        <Text style={styles.practice}>✓ Provide immediate visual feedback</Text>
        <Text style={styles.practice}>✓ Use haptic feedback for touch events</Text>
        <Text style={styles.practice}>✓ Animate state changes smoothly</Text>
        <Text style={styles.practice}>✓ Show gesture boundaries clearly</Text>
        <Text style={styles.practice}>✓ Maintain 60fps during gestures</Text>
      </View>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#F5F5F5',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    textAlign: 'center',
    marginTop: 50,
    marginBottom: 20,
  },
  status: {
    fontSize: 16,
    textAlign: 'center',
    marginBottom: 30,
    color: '#666',
  },
  feedbackBox: {
    width: 150,
    height: 150,
    backgroundColor: '#2196F3',
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
    alignSelf: 'center',
    marginBottom: 40,
  },
  boxText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  practicesList: {
    backgroundColor: 'white',
    padding: 20,
    borderRadius: 10,
  },
  practicesTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 15,
  },
  practice: {
    fontSize: 14,
    marginBottom: 8,
    color: '#333',
  },
});
export default GestureFeedbackExample;Common Patterns
Implementing common gesture patterns used in mobile applications.
Pull-to-Refresh Pattern
import React, { useState, useRef } from 'react';
import { View, Text, StyleSheet, ScrollView } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
const PullToRefreshExample = () => {
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [data, setData] = useState(['Item 1', 'Item 2', 'Item 3']);
  const translateY = useSharedValue(0);
  const refreshThreshold = 100;
  const performRefresh = async () => {
    setIsRefreshing(true);
   
    // Simulate API call
    setTimeout(() => {
      setData(prev => [`Item ${prev.length + 1}`, ...prev]);
      setIsRefreshing(false);
      translateY.value = withSpring(0);
    }, 2000);
  };
  const gestureHandler = useAnimatedGestureHandler({
    onActive: (event) => {
      if (event.translationY > 0) {
        translateY.value = event.translationY;
      }
    },
    onEnd: (event) => {
      if (event.translationY > refreshThreshold) {
        runOnJS(performRefresh)();
      } else {
        translateY.value = withSpring(0);
      }
    },
  });
  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ translateY: translateY.value }],
    };
  });
  const refreshIndicatorStyle = useAnimatedStyle(() => {
    const opacity = Math.min(translateY.value / refreshThreshold, 1);
    const rotation = `${(translateY.value / refreshThreshold) * 360}deg`;
   
    return {
      opacity,
      transform: [{ rotate: rotation }],
    };
  });
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Pull to Refresh</Text>
     
      <View style={styles.refreshContainer}>
        <Animated.View style={[styles.refreshIndicator, refreshIndicatorStyle]}>
          <Text style={styles.refreshText}>
            {isRefreshing ? '🔄 Refreshing...' : '⬇️ Pull to refresh'}
          </Text>
        </Animated.View>
      </View>
     
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.ScrollView
          style={[styles.scrollView, animatedStyle]}
          scrollEnabled={translateY.value === 0}
        >
          {data.map((item, index) => (
            <View key={index} style={styles.item}>
              <Text style={styles.itemText}>{item}</Text>
            </View>
          ))}
        </Animated.ScrollView>
      </PanGestureHandler>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F5F5',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    textAlign: 'center',
    marginTop: 50,
    marginBottom: 20,
  },
  refreshContainer: {
    height: 50,
    justifyContent: 'center',
    alignItems: 'center',
  },
  refreshIndicator: {
    padding: 10,
  },
  refreshText: {
    fontSize: 16,
    color: '#666',
  },
  scrollView: {
    flex: 1,
    backgroundColor: 'white',
  },
  item: {
    padding: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#E0E0E0',
  },
  itemText: {
    fontSize: 16,
  },
});
export default PullToRefreshExample;Troubleshooting
Common issues and solutions when implementing gestures in React Native.
Debugging Gesture Issues
import React, { useState } from 'react';
import { View, Text, StyleSheet, Switch } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import Animated, {
  useAnimatedGestureHandler,
  useAnimatedStyle,
  useSharedValue,
  runOnJS,
} from 'react-native-reanimated';
const GestureDebuggingExample = () => {
  const [debugMode, setDebugMode] = useState(false);
  const [gestureData, setGestureData] = useState({});
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const updateGestureData = (data) => {
    if (debugMode) {
      setGestureData(data);
    }
  };
  const gestureHandler = useAnimatedGestureHandler({
    onStart: (event) => {
      runOnJS(updateGestureData)({
        state: 'START',
        x: event.x,
        y: event.y,
        timestamp: Date.now(),
      });
    },
    onActive: (event) => {
      translateX.value = event.translationX;
      translateY.value = event.translationY;
     
      runOnJS(updateGestureData)({
        state: 'ACTIVE',
        translationX: event.translationX,
        translationY: event.translationY,
        velocityX: event.velocityX,
        velocityY: event.velocityY,
      });
    },
    onEnd: (event) => {
      runOnJS(updateGestureData)({
        state: 'END',
        finalX: event.translationX,
        finalY: event.translationY,
        duration: Date.now() - (gestureData.timestamp || 0),
      });
    },
    onFail: () => {
      runOnJS(updateGestureData)({
        state: 'FAILED',
        reason: 'Gesture recognition failed',
      });
    },
  });
  const animatedStyle = useAnimatedStyle(() => {
    return {
      transform: [
        { translateX: translateX.value },
        { translateY: translateY.value },
      ],
    };
  });
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Gesture Debugging</Text>
     
      <View style={styles.debugToggle}>
        <Text style={styles.debugLabel}>Debug Mode:</Text>
        <Switch
          value={debugMode}
          onValueChange={setDebugMode}
        />
      </View>
     
      <PanGestureHandler onGestureEvent={gestureHandler}>
        <Animated.View style={[styles.debugBox, animatedStyle]}>
          <Text style={styles.boxText}>Debug Gesture</Text>
        </Animated.View>
      </PanGestureHandler>
     
      {debugMode && (
        <View style={styles.debugInfo}>
          <Text style={styles.debugTitle}>Gesture Data:</Text>
          <Text style={styles.debugText}>
            {JSON.stringify(gestureData, null, 2)}
          </Text>
        </View>
      )}
     
      <View style={styles.troubleshootingTips}>
        <Text style={styles.tipsTitle}>Common Issues & Solutions:</Text>
        <Text style={styles.tip}>
          • Gesture not working: Check gesture handler setup
        </Text>
        <Text style={styles.tip}>
          • Poor performance: Use native driver
        </Text>
        <Text style={styles.tip}>
          • Conflicts: Manage gesture priority
        </Text>
        <Text style={styles.tip}>
          • Unexpected behavior: Enable debug mode
        </Text>
      </View>
    </View>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#F5F5F5',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    textAlign: 'center',
    marginTop: 50,
    marginBottom: 20,
  },
  debugToggle: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    marginBottom: 30,
  },
  debugLabel: {
    fontSize: 16,
    marginRight: 10,
  },
  debugBox: {
    width: 120,
    height: 120,
    backgroundColor: '#9C27B0',
    borderRadius: 10,
    justifyContent: 'center',
    alignItems: 'center',
    alignSelf: 'center',
    marginBottom: 20,
  },
  boxText: {
    color: 'white',
    fontSize: 16,
    fontWeight: 'bold',
  },
  debugInfo: {
    backgroundColor: 'white',
    padding: 15,
    borderRadius: 8,
    marginBottom: 20,
  },
  debugTitle: {
    fontSize: 14,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  debugText: {
    fontSize: 12,
    fontFamily: 'monospace',
    color: '#666',
  },
  troubleshootingTips: {
    backgroundColor: 'white',
    padding: 15,
    borderRadius: 8,
  },
  tipsTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  tip: {
    fontSize: 14,
    marginBottom: 5,
    color: '#666',
  },
});
export default GestureDebuggingExample;Summary
In this chapter, we covered comprehensive gesture handling in React Native:
- Basic Touch Events: Understanding onPress, onPressIn, onPressOut, and different touchable components
 - Gesture Responder System: Using PanResponder for basic gesture recognition
 - React Native Gesture Handler: Leveraging the more powerful gesture handling library
 - Common Gestures: Implementing pan, pinch, swipe, and long press gestures
 - Advanced Patterns: Creating custom gestures like drawer navigation and pull-to-refresh
 - Performance Optimization: Best practices for smooth gesture interactions
 - Conflict Resolution: Managing multiple gesture handlers
 - Debugging: Tools and techniques for troubleshooting gesture issues
 
Key Takeaways
- Use React Native Gesture Handler for complex gestures and better performance
 - Always provide visual feedback for user interactions
 - Consider gesture conflicts when implementing multiple handlers
 - Optimize performance by using the native driver and minimizing JS thread usage
 - Test gestures thoroughly on different devices and screen sizes
 - Implement proper accessibility support for gesture-based interactions
 
Next Steps
In the next chapter, we’ll explore React Native Hooks, which provide a powerful way to manage state and side effects in functional components, including gesture-related state management.
Related Articles:
- React Native vs Flutter in 2024: A Detailed Comparison
 - Top 10 React Native Libraries You Must Know in 2024
 - React vs React Native: A Deep Dive into Key Differences
 - How to Set Up Navigation in React Native : Step-by-Step Guide
 - What is Axios? Fetch vs Axios: What’s the Difference?
 - React Native Environment Setup: A Beginner’s Step-by-Step Guide
 - React Native Web: A Step-by-Step Guide to Cross-Platform Development