Implementing a WhatsApp like Swipe to Delete Feature in React Native

Swipe to delete has become a very common feature present in most mobile apps today. If done correctly, it feels like all’s right with the world. If not, it can turn into a nightmarish experience for the user resulting in countless recipes and playlist songs unexpectedly disappearing into the ether.

In this post we’ll explore how we can create a smooth, responsive and intuitive swipe to delete in React Native and what challenges and limitations we might face. I am a big fan of the WhatsApp implementation so we’ll aim for a similar look and feel.

In order to deliver on our promise, here’s some of the criteria our design needs to meet.

  • Provide visual and sensory cues to signal to users what action is taking place and what the consequences of following through on that action will be
  • Allow users to change their mind mid swipe and cancel the action
  • Provide feedback after a swipe has been completed
  • Achieve smooth and seamless transitions and animations to deliver a great user experience

Here is what our end product will look like:

We’ll be using a third-party library called react-native-swipe-list-view which gives us a Swipe List component in conjunction with React Native’s Animated API for more granular control over our animations.

You can find the complete example on Github.

If you prefer a longer video format check out this video:

Let’s create a new React Native project and install the library.

npx react-native init swipeToDelete
cd swipeToDelete
npx react-native run-ios
npm i react-native-swipe-list-view
npx pod-install

We’ll import out <SwipeListView /> component and generate some dummy list data to pass to the data prop. The view also requires a renderItem and renderHiddenItem props to render a front row and a hidden row which is revealed when the user swipes and contains our action buttons. We’ll pass a function to each prop which accepts 2 parameters — rowData and rowMap and returns a React element. rowData as the name suggests is the extracted data for an individual row from the data array we passed earlier where as rowMap is an object that looks like this

{
row_key_1: ref_to_row_1,
row_key_2: ref_to_row_2
}

and contains a reference to the row which can be used to access helpful methods like closeRow to swipe a row closed programatically. The row key is the same key we are passing through our data array or if one is not defined, it will use the key generated by the keyExtractor prop.

We are also adding the disableRightSwipe prop to disable swiping from left to right for simplicity sake and setting the rightOpenValue prop to -120 which is the translate value for the front row along the x-axis when the row is opened. The value will always be negative since we want to shift the front row to the left to reveal the hidden row on the right side.

Next, we’ll add the <VisibleItem /> and <HiddenItemWithActions /> components which are returned from the renderItem and renderHiddenItem functions. They are responsible for rendering the content of the front and back row. They can also react to changes in the swipe state and show that some action has been activated, e.g. expand the delete button to full width of the row. To enable this behaviour we also need to set some props to configure these actions.

We’ll need some icons too.

npm i react-native-svg
npx pod-install
npm i react-native-eva-icons

We are utilising the rightActivationValue prop as an indicator to signify to the user that an important action is able to take place should they decide to proceed. Once the swipe value exceeds the rightActivationValue it will fire off the onRightActionStatusChange function and activate the rightActionActivated value which is another prop we can pass to the hidden row component and make use of to animate the delete button expanding to the full width of the row or contacting to its initial width if the action is cancelled.

The swipeGestureEnded prop takes a function which is called when the user has ended their swipe gesture and can be used to animate the row being deleted and to provide confirmation of the successful completion of the action. The rightActionValue is the translateX value to which the row will be shifted after the gesture is released which in our case is the whole width of the screen so that the front row disappears completely. The swipeToOpenPercent / swipeToClosePercent props are the percentage of the rightOpenValue the user needs to swipe past to trigger the row opening / closing.

We are also setting the useNativeDriver prop to false because we’ll be animating layout properties that are not supported by the native driver such as the height and width of the row.

In order to animate the rows and buttons we need to do a refactor of the visible and hidden component and wrap some of the content with animatable components. We’ll create a list to store the animated values for the row height and the delete button width for all rows. We’ll also want to animate the translateX property of the buttons to create a smooth in and out of view transition. For that purpose we can make use of another prop passed to the hidden component called swipeAnimatedValue which gives us direct access to the swipe row translateX animated value. We can interpolate that value to get the transition just the way we want it.

When the swipe gesture is released if the translateX swipe value exceeds the rightActivationValue (< -200) the row will be shifted to our rightActionValue and disappear from the viewport. We’ll also use the Animated.timing() API to animate the width of the delete button to fill the screen width and the row height to 0. We’ll pass a callback function to the timing method which will remove the respective item from the list immediately after the animation runs making the whole row disappear completely.

For the cherry on top, we’ll provide some haptic feedback the moment the user swipes past the activation value to drive home the message that something important is taking place.

npm i react-native-haptic-feedback
npx pod-install

While this swipe list works like a charm with small lists, it doesn’t fare especially well with big ones. Here are some tips to work around this limitation:

  • Make sure to optimise your <FlatList /> which is what <SwipeListView /> uses under the hood.
  • Design animations to use only non-layout properties like transform and opacity which are supported by the native driver. In our example, we could have animated the scaleY property of the row instead of the height to have it disappear.
  • react-native-swipe-list-view uses React Native’s Animated library and the creator has no intention of migrating to Reanimated. Find an alternative swipe list package based on Reanimated in order to offload animation and event handling logic off of the JavaScript thread and onto the UI thread.

Hope you enjoyed the read :)