Ejiro Asiuwhu Software Engineer with a drive for building highly scalable and performant web applications. Heavily interested in module federation, micro frontends, state machines, TDD, and system designs. Big on web performance and optimization, advanced component design patterns, a11y, SSR, SSG Incremental Static Regeneration (ISR), and state management. Expert at crafting highly reusable Typescript-heavy component libraries. An advocate of TypeScript and industry best practices. I regularly author meaningful technical content ✍🏽.

React Native Gesture Handler: Swipe, long-press, and more

8 min read 2488

React Native Logo

Most apps nowadays allow users to interact with key components by swiping, double-tapping, pinching, long-pressing, etc. When properly implemented in your mobile app, gestures provide your users with an engaging, natural, and intuitive experience. In fact, you can replace most visible controls, such as buttons and icon clicks, with gestures.

There are several packages you can use to implement gestures in a React Native app. The most popular and recommended library is react-native-gesture-handler. This library exposes platform-specific (i.e., Android and iOS) native touch and gestures to React Native.

Although gestures in React Native can be handled using the built-in Gesture Responder System, this implementation has some limitations because it runs on the JavaScript thread. So every time a gesture event is carried out, it sends the data across the React Native bridge to the interface, which can lead to poor performance.

React Native Gesture Handler enables you to implement highly performant gestures in React Native because it runs in the native thread and follows platform-specific behavior, which in turn leads to better performance.

The React Native Gesture Handler library comes with lots of useful gestures, including:

  • PanGestureHandler
  • TapGestureHandler
  • LongPressGestureHandler
  • PinchGestureHandler

In addition to the above gestures, we’ll also demonstrate how to implement the pull-to-refresh, swipeable, and double-tap gestures.

Initializing a new Expo app

We’ll start by initializing a new Expo app. If you don’t have Expo installed on your machine already, run the command below:

# installing expo CLI globally
npm install --global expo-cli

To create a new Expo app, run the following command on your terminal

# Create a project named react-native-gestures

expo init react-native-gestures

Navigate to your project directory and run the following command to start your application:

expo start

Press i to open in an iOS simulator, or press a to open in an Android emulator or connected device. Make sure your simulator or emulator is set up already.

Now let’s get started implementing and managing gestures in our React Native app.

Pan gesture

To implement a pan gesture in React Native using the react-native-gesture-handler library, we’ll use PanGestureHandler. PanGestureHandler is a continuous gesture handler that generates streams of gesture events when the user pans (drags) an element.

To get started with PanGestureHandler, we have to import it from the react-native-gesture-handler library we installed earlier:

import { PanGestureHandler } from 'react-native-gesture-handler';

Next, we need to wrap the element to which we want to apply the pan gesture with the PanGestureHandler component we imported earlier:

// in your return block
<PanGestureHandler>
  <View style={styles.square}/>
</PanGestureHandler>

To make things move around, we need to use the base gesture handler props called onGestureEvent and pass a callback function to it.

Let’s create a function, which we’ll later pass to the onGestureEvent:

// dont't forget to import Animated from react native
onPanGestureEvent = Animated.event(
    [
      {
        nativeEvent: {
          translationX: this.translateX,
          translationY: this.translateY,
        },
      },
    ],
    { useNativeDriver: true }
  );

Note that we’re using a class component here.

Let’s pass the function we just created to the onGestureEvent prop in the PanGestureHandler component.

<PanGestureHandler onGestureEvent={this.onPanGestureEvent}> </PanGestureHandler>

Since we want our View component to animate, let’s replace it with Animated.View and add the translateX and translateY properties in our transform array to specify the translation of the pan gesture along the X and Y axes.

  <Animated.View
            style={[
              styles.square,
              {
                transform: [
                  {
                    translateX: this.translateX,
                  },
                  {
                    translateY: this.translateY,
                  },
                ],
              },
            ]}
          />
Pan Gesture
Pan Gesture in React Native.

Here’s the complete code for creating pan gestures with React Native Gesture Handler:

import React, { Component } from 'react';
import { StatusBar } from 'expo-status-bar';
import { PanGestureHandler } from 'react-native-gesture-handler';
import { Animated, StyleSheet, Text } from 'react-native';
export default class PanGesture extends Component {
  translateX = new Animated.Value(0);
  translateY = new Animated.Value(0);
  onPanGestureEvent = Animated.event(
    [
      {
        nativeEvent: {
          translationX: this.translateX,
          translationY: this.translateY,
        },
      },
    ],
    { useNativeDriver: true }
  );
  render() {
    return (
      <>
        <Text>Pan Gesture Handler</Text>
        <PanGestureHandler onGestureEvent={this.onPanGestureEvent}>
          <Animated.View
            style={[
              styles.square,
              {
                transform: [
                  {
                    translateX: this.translateX,
                  },
                  {
                    translateY: this.translateY,
                  },
                ],
              },
            ]}
          />
        </PanGestureHandler>
        <StatusBar style="auto" />
      </>
    );
  }
}
const styles = StyleSheet.create({
  square: {
    width: 150,
    height: 150,
    backgroundColor: '#28b5b5',
    marginTop: 22,
  },
});

TapGestureHandler

With TapGestureHandler, we can implement both single- and double-tap gestures. Let’s start with single-tap gesture and then move on to a double-tap gesture.

Single-tap gesture

Single Tap Gesture
Single-tap gesture with TapGestureHandler using React Native Gesture Handler.

We’ll implement the single-tap gesture using the TapGestureHandler component. Notice we’re adding onHandlerStateChange props and passing a function:

import { View, StyleSheet, Text } from 'react-native';
import { TapGestureHandler, State } from 'react-native-gesture-handler';

export default function TapGesture() {

  const onSingleTapEvent = (event) => {
    if (event.nativeEvent.state === State.ACTIVE) {
      alert('Hey single tap!');
    }
  };

  return (
    <>
      <Text>Double and Single Tap Gesture Handler</Text>
      <TapGestureHandler
        onHandlerStateChange={onSingleTapEvent}
        waitFor={doubleTapRef}
      >
        <View style={styles.square} />
      </TapGestureHandler>
    </>
  );
}

Double-tap

Double-tap Gesture
Double-tap gesture with TapGestureHandler using React Native Gesture Handler.

The major difference between the single- and double-tap gesture implementation is the numberOfTaps props, which is passed to the TapGestureHandler component. Notice we’re passing 2 to numberOfTaps to specify that we want to trigger the onHandlerStateChange event callback:

import React, { useRef, useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { TapGestureHandler, State } from 'react-native-gesture-handler';

export default function TapGesture() {

  const [likeColour, setLikeColour] = useState('#28b5b5');
  const doubleTapRef = useRef(null);

  const onDoubleTapEvent = (event) => {
    if (event.nativeEvent.state === State.ACTIVE) {
      likeColour === '#28b5b5'
        ? setLikeColour('red')
        : setLikeColour('#28b5b5');
    }
  };
  const styles = StyleSheet.create({
    square: {
      width: 150,
      height: 150,
      backgroundColor: likeColour,
      marginTop: 22,
      marginBottom: 22,
    },
  });
  return (
    <>
      <Text>Double and Single Tap Gesture Handler</Text>
        <TapGestureHandler
          ref={doubleTapRef}
          onHandlerStateChange={onDoubleTapEvent}
          numberOfTaps={2}
        >
          <View style={styles.square} />
      </TapGestureHandler>
    </>
  );
}

Note that we’re using a functional component here because we’re using the useRef hook.



Implementing single- and double-tap in one component

Double- and Single-tap Gestures
Double and single tap gesture with TapGestureHandler using React Native Gesture Handler.

With TapGestureHandler, we can implement a gesture where an event is called on single-tap and another on double-tap. A good example of this is the LinkedIn mobile app, where a single click opens the post and a double-click likes the post.

import React, { useRef, useState } from 'react';
import { View, StyleSheet, Text } from 'react-native';
import { TapGestureHandler, State } from 'react-native-gesture-handler';

export default function TapGesture() {

  const [likeColour, setLikeColour] = useState('#28b5b5');
  const doubleTapRef = useRef(null);

  const onSingleTapEvent = (event) => {
    if (event.nativeEvent.state === State.ACTIVE) {
      alert('Hey single');
    }
  };

  const onDoubleTapEvent = (event) => {
    if (event.nativeEvent.state === State.ACTIVE) {
      likeColour === '#28b5b5'
        ? setLikeColour('red')
        : setLikeColour('#28b5b5');
    }
  };

  const styles = StyleSheet.create({
    square: {
      width: 150,
      height: 150,
      backgroundColor: likeColour,
      marginTop: 22,
      marginBottom: 22,
    },
  });

  return (
    <>
      <Text>Double and Single Tap Gesture Handler</Text>
      <TapGestureHandler
        onHandlerStateChange={onSingleTapEvent}
        waitFor={doubleTapRef}
      >
        <TapGestureHandler
          ref={doubleTapRef}
          onHandlerStateChange={onDoubleTapEvent}
          numberOfTaps={2}
        >
          <View style={styles.square} />
        </TapGestureHandler>
      </TapGestureHandler>
    </>
  );
}

To make this work, we need to add the waitFor={doubleTapRef} to the single tap TapGestureHandler component. When the doubleTapRef is truthy, the on onSingleTapEvent will not be called.

Swipeable gesture

Swipeable Gestures
Swipeable gestures with Swipeable component using React Native Gesture Handle.

To demonstrate how to implement a swipeable gesture, let’s create a list of items where users can swipe right or left and certain events or methods are called.

Let’s create a flat list component and pass in our data to the data props:

<FlatList
          data={todoList}
          keyExtractor={(item) => item.id}
          renderItem={({ item }) => <ListItem {...item} />}
          ItemSeparatorComponent={() => <Separator />}
        />

Notice that the renderItem props is returning a ListItem component. This represents our list todo items, as shown in the demo above.

Now let’s create our ListItem component and make it swipeable.

First, import the Swipeable component from the react-native-gesture-handler package:

import Swipeable from 'react-native-gesture-handler/Swipeable';

Next, wrap the View and Text components with the Swipeable component we imported earlier. With this in place, our component is swipeable. But in a real-world scenario, you would want some kind of event or function to be called when the user swipes left or right.

To render containers on the left, use the renderLeftActions props and pass in a component. To render containers on the right, use the renderRightActions props.

On swipe to left, we have the delete container and on swipe to right, we have the bookmark container. The renderLeftActions and renderRightActions props make this possible.

The onSwipeableRightOpen props accepts a method that is called when the swipeable gesture from right to left is complete — i.e., when the right action panel is opened. The onSwipeableLeftOpen props accepts a method that is called when the left action panel is opened.

const ListItem = ({ text }) => (
  <Swipeable
    renderLeftActions={LeftSwipeActions}
    renderRightActions={rightSwipeActions}
    onSwipeableRightOpen={swipeFromRightOpen}
    onSwipeableLeftOpen={swipeFromLeftOpen}
  >
    <View
      style={{
        paddingHorizontal: 30,
        paddingVertical: 20,
        backgroundColor: 'white',
      }}
    >
      <Text style={{ fontSize: 24 }} style={{ fontSize: 20 }}>
        {text}
      </Text>
    </View>
  </Swipeable>
);

Here is the full code for the swipeable gesture:

import React from 'react';
import {
  SafeAreaView,
  StyleSheet,
  View,
  Text,
  StatusBar,
  FlatList,
} from 'react-native';
import Swipeable from 'react-native-gesture-handler/Swipeable';
const todoList = [
  { id: '1', text: 'Learn JavaScript' },
  { id: '2', text: 'Learn React' },
  { id: '3', text: 'Learn TypeScript' },
];
const Separator = () => <View style={styles.itemSeparator} />;
const LeftSwipeActions = () => {
  return (
    <View
      style={{ flex: 1, backgroundColor: '#ccffbd', justifyContent: 'center' }}
    >
      <Text
        style={{
          color: '#40394a',
          paddingHorizontal: 10,
          fontWeight: '600',
          paddingHorizontal: 30,
          paddingVertical: 20,
        }}
      >
        Bookmark
      </Text>
    </View>
  );
};
const rightSwipeActions = () => {
  return (
    <View
      style={{
        backgroundColor: '#ff8303',
        justifyContent: 'center',
        alignItems: 'flex-end',
      }}
    >
      <Text
        style={{
          color: '#1b1a17',
          paddingHorizontal: 10,
          fontWeight: '600',
          paddingHorizontal: 30,
          paddingVertical: 20,
        }}
      >
        Delete
      </Text>
    </View>
  );
};
const swipeFromLeftOpen = () => {
  alert('Swipe from left');
};
const swipeFromRightOpen = () => {
  alert('Swipe from right');
};
const ListItem = ({ text }) => (
  <Swipeable
    renderLeftActions={LeftSwipeActions}
    renderRightActions={rightSwipeActions}
    onSwipeableRightOpen={swipeFromRightOpen}
    onSwipeableLeftOpen={swipeFromLeftOpen}
  >
    <View
      style={{
        paddingHorizontal: 30,
        paddingVertical: 20,
        backgroundColor: 'white',
      }}
    >
      <Text style={{ fontSize: 24 }} style={{ fontSize: 20 }}>
        {text}
      </Text>
    </View>
  </Swipeable>
);
const SwipeGesture = () => {
  return (
    <>
      <StatusBar />
      <SafeAreaView style={styles.container}>
        <Text style={{ textAlign: 'center', marginVertical: 20 }}>
          Swipe right or left
        </Text>
        <FlatList
          data={todoList}
          keyExtractor={(item) => item.id}
          renderItem={({ item }) => <ListItem {...item} />}
          ItemSeparatorComponent={() => <Separator />}
        />
      </SafeAreaView>
    </>
  );
};
const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  itemSeparator: {
    flex: 1,
    height: 1,
    backgroundColor: '#444',
  },
});
export default SwipeGesture;

Hold/long-press gesture

Hold Gesture
Long press / hold gestures with LongPressGestureHandler using React Native Gesture Handle.

With React Native Gesture Handler, implementing the long press/hold gesture is straightforward.

Basically, we need to wrap the React Native component we want to implement the gesture on with LongPressGestureHandler, which is imported from react-native-gesture-handler, and then add the onHandlerStateChange props, which triggers a method when the user holds on the component for a given duration. To specify the duration of the long press, we pass in the minDurationMs, which accepts a number expressed in milliseconds.

Here is the code for the demo shown above:

import React from 'react';
import { View, StyleSheet } from 'react-native';
import { LongPressGestureHandler, State } from 'react-native-gesture-handler';

export default function LongPressGesture() {
  const onLongPress = (event) => {
    if (event.nativeEvent.state === State.ACTIVE) {
      alert("I've been pressed for 800 milliseconds");
    }
  };

  return (
    <LongPressGestureHandler
      onHandlerStateChange={onLongPress}
      minDurationMs={800}
    >
      <View style={styles.box} />
    </LongPressGestureHandler>
  );
}
const styles = StyleSheet.create({
  box: {
    width: 150,
    height: 150,
    backgroundColor: '#28b5b5',
    marginTop: 22,
    marginBottom: 22,
  },
});

Pinch-to-zoom

Just like the name implies, this is a continuous gesture that tracks the distance between two fingers and uses that information to scale or zoom your content.

render() {
    return (
      <PinchGestureHandler
        onGestureEvent={this.onPinchGestureEvent}
        onHandlerStateChange={this.onPinchHandlerStateChange}
      >
        <Animated.View
          style={[
            styles.pinchableImage,
            {
              transform: [{ perspective: 1 }, { scale: this.scale }],
            },
          ]}
        ></Animated.View>
      </PinchGestureHandler>
    );
  }
Pinch-to-zoom Gesture
Pinch to zoom gestures with PinchGestureHandler using React Native Gesture Handle.

To make this work, we need to add the onGestureEvent props to our PinchGestureHandler and then set the { scale: this.scale } object in our transform array:

import React, { Component } from 'react';
import { View, Image, StyleSheet, Animated } from 'react-native';
import { PinchGestureHandler, State } from 'react-native-gesture-handler';
export default class PinchToZoom extends Component {
  baseScale = new Animated.Value(1);
  pinchScale = new Animated.Value(1);
  scale = Animated.multiply(this.baseScale, this.pinchScale);
  lastScale = 1;
  onPinchGestureEvent = Animated.event(
    [{ nativeEvent: { scale: this.pinchScale } }],
    { useNativeDriver: true }
  );
  onPinchHandlerStateChange = (event) => {
    if (event.nativeEvent.oldState === State.ACTIVE) {
      this.lastScale *= event.nativeEvent.scale;
      this.baseScale.setValue(this.lastScale);
      this.pinchScale.setValue(1);
    }
  };
  render() {
    return (
      <PinchGestureHandler
        onGestureEvent={this.onPinchGestureEvent}
        onHandlerStateChange={this.onPinchHandlerStateChange}
      >
        <Animated.View
          style={[
            styles.pinchableImage,
            {
              transform: [{ perspective: 1 }, { scale: this.scale }],
            },
          ]}
        ></Animated.View>
      </PinchGestureHandler>
    );
  }
}
const styles = StyleSheet.create({
  pinchableImage: {
    width: 250,
    height: 250,
    backgroundColor: '#28b5b5',
    marginTop: 22,
    marginBottom: 22,
  },
});

Pull-to-refresh

To implement pull-to-refresh in React Native, you don’t need an external library. Just add the onRefresh props, which accepts a function in the FlatList component and also sets the refreshing props to a boolean.

// at the top of your component
  const [refreshing, setrefreshing] = useState(false);

<FlatList
        data={data}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        refreshing={refreshing}
        onRefresh={onRefresh}
      />

By default, we’re setting refreshing to false and then, when the onRefresh method is called, we set the refreshing state to true using setrefreshing:

Pull-to-refresh Gesture
Pull-to-refresh gesture demo in React Native.

Here’s the full code for the demo above:

import React, { useState } from 'react';
import {
  SafeAreaView,
  View,
  FlatList,
  StyleSheet,
  Text,
  StatusBar,
} from 'react-native';
const DATA = [
  {
    id: '1',
    title: 'Take coffee',
  },
  {
    id: '2',
    title: 'Write some code',
  },
  {
    id: '3',
    title: 'Take the test',
  },
  {
    id: '4',
    title: 'Excercise',
  },
];
const Item = ({ title }) => (
  <View style={styles.item}>
    <Text style={styles.title}>{title}</Text>
  </View>
);
export default function PullToRefresh() {
  const [refreshing, setrefreshing] = useState(false);
  const [data, setdata] = useState(DATA);
  const onRefresh = () => {
    setrefreshing(true);
    setTimeout(() => {
      setdata((data) => [
        ...data,
        {
          id: '57878',
          title: 'Take a walk in the park',
        },
      ]);
      setrefreshing(false);
    }, 2000);
  };
  const renderItem = ({ item }) => <Item title={item.title} />;
  return (
    <SafeAreaView style={styles.container}>
      <FlatList
        data={data}
        renderItem={renderItem}
        keyExtractor={(item) => item.id}
        refreshing={refreshing}
        onRefresh={onRefresh}
      />
    </SafeAreaView>
  );
}
const styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop: StatusBar.currentHeight || 0,
  },
  item: {
    backgroundColor: '#fad586',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 20,
  },
});

Conclusion

Implementing gestures in React Native can help improve the user experience and make your app feel natural to users.

In this tutorial, we covered the implementation and management of gestures in a React Native app, including swipeable, pan, double- and single-tap, pinch-to-zoom, and more.

All the code for this demo is available on GitHub.

LogRocket: Instantly recreate issues in your React Native apps.

LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.

LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.

Start proactively monitoring your React Native apps — .

Ejiro Asiuwhu Software Engineer with a drive for building highly scalable and performant web applications. Heavily interested in module federation, micro frontends, state machines, TDD, and system designs. Big on web performance and optimization, advanced component design patterns, a11y, SSR, SSG Incremental Static Regeneration (ISR), and state management. Expert at crafting highly reusable Typescript-heavy component libraries. An advocate of TypeScript and industry best practices. I regularly author meaningful technical content ✍🏽.

2 Replies to “React Native Gesture Handler: Swipe, long-press, and more”

  1. What is the difference between LongPressGestureHandler and onLongPress event handler in TouchableWithoutFeedback?

Leave a Reply