React Native: make a draggable & swipeable list

  • Tutorial
Today it’s hard to surprise anyone with the ability to swipe list items in mobile applications. One of our react-native applications also had such functionality, but recently there was a need to expand it with the ability to drag and drop list items. And since the process of finding a solution cost me a certain amount of nerve cells, I decided to file a short article to save valuable time for future generations.



In our application, we used the package to create a swipeable list react-native-swipe-list-view. The first thought was to take some package with drag'n'drop functionality and cross the hedgehog with a snake.

Search across the expanses of the Internet has given the three candidates: react-native-draggable-list, react-native-sortable-listand react-native-draggable-flatlist.

Using the first package, it was not possible to run even the attached example (however, not only to me, the corresponding issue is indicated in issues ).

I had to tinker with the second package, but I managed to create a draggable & swipable list. However, the result was not inspired - the component was shamelessly blinking: blinking of the redrawing, falling of elements far beyond the list, or even their disappearance. It became clear that in this form it can not be used.

At first, the last package also behaved like a capricious lady, but then it turned out that I simply did not know how to cook it. Having picked up a key to the heart of this "lady", I managed to achieve an acceptable result.

In our project, there was a swipeable list to which you need to fasten drag and drop, but in practice it is better to start from the other edge: first make a draggable list and then add the ability to swipe.

Readers are supposed to know how to create a react-native project, so let's focus on creating the list we need. The example discussed below is TypeScript code.

Making a draggable-list


So, let's start by installing the package:

yarn add react-native-draggable-flatlist

We import the necessary modules:

import React, { Component } from 'react'
import { View } from 'react-native'
import styles from './styles'
import DraggableFlatList, { RenderItemInfo, OnMoveEndInfo } from 'react-native-draggable-flatlist'
import ListItem from './components/ListItem'
import fakeData from './fakeData.json'

Here DraggableFlatListis the component from the installed package that implements the drag and drop function, ListItemour component for displaying the list item (the code will be presented below), the fakeDatajson file containing the fake data - in this case, an array of objects of the form:

{"id": 0, "name": "JavaScript", "favorite": false}

In a real application, this data will most likely come to your component from props or will be downloaded from the network, but in our case we will manage with little blood.

Since TypeScript is used in this example, we describe some entities:

type Language = {
  id: number,
  name: string,
  favorite: boolean,
}
interface AppProps {}
interface AppState {
  data: Array
}

The type Languagetells us which fields the list items will have.

In this example, we will not get anything from props, so the interface AppPropsis trivial, and in the story we will store an array of objects Language, which is indicated in the interface AppState.

Since the component code is not very large, I will give it in its entirety:

App component code
class App extends Component {
  constructor(props: AppProps) {
    super(props)
    this.state = {
      data: fakeData,
    }
  }
  onMoveEnd = ({ data }: OnMoveEndInfo) => {
    this.setState({ data: data ? [...data] : [] })
  }
  render() {
    return (
       item.id.toString()}
          scrollPercent={5}
          onMoveEnd={this.onMoveEnd}
        />
      
    )
  }
  renderItem = ({ item,  move, moveEnd, isActive }: RenderItemInfo) => {
    return (
      
    )
  }
}


The method onMoveEndis called when the movement of the element is finished. In this case, we need to put the list with the new order of elements in the state, so we call the method this.setState.

The method renderItemis used to display a list item and takes an object of type RenderItemInfo. This object includes the following fields:

  • item - the next element of the array passed as data to the list,
  • moveand moveEnd- the functions called when the list item is moved, the component provides these functions DraggableFlatList,
  • isActive - a field of a logical type that determines whether the item is draggable at the moment.

The component for displaying the list item, in fact, is TouchableOpacitywhich, when pressed long, calls move, and when released, - moveEnd.

ListItem component code
import React from 'react'
import { Text, TouchableOpacity } from 'react-native'
import styles from './styles'
interface ListItemProps {
  name: string,
  move: () => void,
  moveEnd: () => void,
  isActive: boolean,
}
const ListItem = ({ name, move, moveEnd, isActive }: ListItemProps) => {
  return (
    {name}
  )
}
export default ListItem


Styles for all components are moved to separate files and are not given here, but they can be viewed in the repository .

The resulting result:



Add the ability to swipe


Well, we successfully coped with the first part, we proceed to the second part of the Marlezon ballet.

To add the ability to swipe the list items, we use the package react-native-swipe-list-view.

To get started, let's install it:

yarn add react-native-swipe-list-view

There is a component in this package SwipeRow, which, according to the documentation, should include two components:


Note that the first View is drawn under the second.

Let's change the component code ListItem.

ListItem component code
import React from 'react'
import { Text, TouchableOpacity, View, Image } from 'react-native'
import { SwipeRow } from 'react-native-swipe-list-view'
import { Language } from '../../App'
import styles from './styles'
const heart = require('./icons8-heart-24.png')
const filledHeart = require('./icons8-heart-24-filled.png')
interface ListItemProps {
  item: Language,
  move: () => void,
  moveEnd: () => void,
  isActive: boolean,
  onHeartPress: () => void,
}
const ListItem = ({ item, move, moveEnd, isActive, onHeartPress }: ListItemProps) => {
  return (
    {item.name}
  )
}
export default ListItem


First, we added a component SwipeRowwith a property rightOpenValuethat determines the distance over which the element can be swiped.

Secondly, we moved SwipeRowours inside TouchableOpacityand added the View, which will be drawn under this button.

Inside this View, a picture is drawn to determine if the language is a favorite. When you click on it, the value should be reversed, and since the data is in the parent component, you need to throw a callback here that performs this action.

Make the necessary changes to the parent component:

App component code
import React, { Component } from 'react'
import { View } from 'react-native'
import styles from './styles'
import DraggableFlatList, { RenderItemInfo, OnMoveEndInfo } from 'react-native-draggable-flatlist'
import ListItem from './components/ListItem'
import fakeData from './fakeData.json'
export type Language = {
  id: number,
  name: string,
  favorite: boolean,
}
interface AppProps {}
interface AppState {
  data: Array
}
class App extends Component {
  constructor(props: AppProps) {
    super(props)
    this.state = {
      data: fakeData,
    }
  }
  onMoveEnd = ({ data }: OnMoveEndInfo) => {
    this.setState({ data: data ? [...data] : [] })
  }
  toggleFavorite = (value: Language) => {
    const data = this.state.data.map(item => (
      item.id !== value.id ? item : { ...item, favorite: !item.favorite }
    ))
    this.setState({ data })
  }
  render() {
    return (
       item.id.toString()}
          scrollPercent={5}
          onMoveEnd={this.onMoveEnd}
        />
      
    )
  }
  renderItem = ({ item, move, moveEnd, isActive }: RenderItemInfo) => {
    return (
       this.toggleFavorite(item)}
      />
    )
  }
}
export default App


Sources of the project on GitHub .

The result is presented below:


Also popular now: