react-native-draglist
FlatList that can be reordered by dragging its items
Why Does This Exist At All?
Given react-native-draggable-flatlist, why is there also this package?
Great question. react-native-draggable-flatlist
has silky-smooth animations, supports horizontal lists as well, contains dozens of code files, and even manipulates internal data structures in react-native-reanimated to make the animations work. You should absolutely use, and prefer, react-native-draggable-flatlist
, if it works for you.
react-native-draglist
exists because react-native-reanimated
, which react-native-draggable-flatlist
depends on, randomly hangs and crashes apps through a variety of issues, several of which have not been fixed despite several major “stable” releases. Furthermore, the hangs and crashes are both frequent and hard to reliably reproduce, making their timely resolution unlikely.
What Is react-native-draglist
This package is a basic version of react-native-draggable-flatlist
without dependencies on anything except react
and react-native
. Specifically, it is deliberately built to avoid react-native-reanimated
and its hanging/crashing issues.
It is limited in the following ways:
- It does not animate as smoothly (though it does
useNativeDriver
) - It only handles vertical lists (though adding horizontal in a PR won’t be hard)
Installation
With no dependencies outside of react-native
and react
, this package installs super easily:
npm install react-native-draglist
or
yarn add react-native-draglist
Use
All FlatList
properties are supported, with the following extensions/modifications:
renderItem
is now passed aDragListRenderItemInfo
, which extendsListRenderItemInfo
with these additional fields:
Field | Type | Note |
---|---|---|
onStartDrag |
() => void |
Your item should call this function when you detect a drag starting (i.e. when the user wants to begin reordering the list). A common implementation is to have a drag handle on your item whose onPress calls onStartDrag . Alternatively, you could have an onLongPress call this, or use any other mechanism that makes most sense for your UI. DragList will not start rendering items as being dragged until you call this. |
isActive |
boolean |
This is true iff the current item is actively being dragged by the user. This can be used to render the item differently while it’s being dragged (e.g. less opacity, different background color, borders, etc). |
async onReordered(fromIndex: number, toIndex: number)
is called once the user drops a dragged item in its new position. This is not called if the user drops the item back in the spot it started.DragList
will await this function, and not reset its UI until it completes, so that you can make modifications to the underlying data before the list resets its state.fromIndex
will be between0
anddata.length
(the total number of items you gaveDragList
to render).toIndex
reflects the position to which the item should be moved in the pre-modifieddata
. It will never equalfromIndex
. So, for instance, iftoIndex
is0
, you should makedata[fromIndex]
the first element ofdata
. Note: if the user drags the item to the very end of the list,toIndex
will equaldata.length
(i.e. it will reference an index that is one beyond the end of the list).
Typical Flow
- Set up
DragList
much like you do anyFlatList
, except with arenderItem
that callsonStartDrag
at the appropriate time. - When
onReordered
gets called, update the ordering ofdata
.
That’s basically it.
Show Me The Code
import React, {useState} from "react";
import {TouchableOpacity} from "react-native";
import DragList, { DragListRenderItemInfo } from "react-native-draglist";
const SOUND_OF_SILENCE = ["hello","darkness","my","old","friend"];
function DraggableLyrics() {
const [data, setData] = useState(SOUND_OF_SILENCE);
function keyExtractor(str: string) {
return str;
}
function renderItem(info: DragListRenderItemInfo) {
const {item, onStartDrag, isActive} = info;
return (
<TouchableOpacity
key={item}
style={{backgroundColor: isActive ? "blue" : "black"}}
onPress={onStartDrag}
>
<Text>{item}</Text>
</TouchableOpacity>
);
}
async function onReordered(fromIndex: number, toIndex: number) {
// Since we remove the element first, account for its index shift
const finalIndex = fromIndex < toIndex ? toIndex - 1 : toIndex;
const copy = [...data]; // Don't modify react data in-place
const removed = copy.splice(fromIndex, 1);
copy.splice(finalIndex, 0, removed[0]); // Now insert at the new pos
setData(copy);
}
return (
<DragList
data={data}
keyExtractor={keyExtractor}
onReordered={onReordered}
renderItem={renderItem}
/>
);
}
Caveats
This package is implemented with probably 1/10th the files, and 1/20th the advanced concepts, as react-native-draggable-flatlist
. The latter even directly modifies unpublished internal data structures of react-native-reanimated
, so it’s all sorts of advanced in ways that this package will never be. You should prefer, and default to, using react-native-draggable-flatlist
unless its random hangs and crashes bother you.
If you have suggestions, or better yet, PRs for how this package can be improved, please connect via GitHub!