react-native-reanimated-drag-list
v0.6.2
Published
High performance draggable list for Fabric using Reanimated 4
Downloads
75
Maintainers
Readme
react-native-reanimated-drag-list
A high-performance draggable list component for React Native, built with Reanimated 4 and Gesture Handler. Runs entirely on the UI thread for buttery smooth 60fps animations.
Features
- 🚀 UI Thread Performance - All animations run on the UI thread via Reanimated
- 📜 Progressive Auto-scroll - Automatically scrolls when dragging near edges with exponential speed curve
- ⏱️ Long Press Activation - Hold to drag, tap to scroll - configurable delay
- 🎯 Smooth Animations - Spring animations for natural feeling interactions
- 📱 Fabric Ready - Built for the new React Native architecture
- 🪆 Nestable Lists - Multiple draggable lists within a single scrollable container
- 📏 Dynamic Heights - Support for items with variable heights
- 🚫 Drag Disabled Zones - Exclude interactive elements from triggering drag
Requirements
- React Native 0.71+
- react-native-reanimated 4.x
- react-native-gesture-handler 2.x
- react-native-worklets
Installation
npm install react-native-reanimated-drag-listMake sure you have the peer dependencies installed:
npm install react-native-reanimated react-native-gesture-handler react-native-workletsUsage
Basic Usage
import { DraggableList, type RenderItemParams } from 'react-native-reanimated-drag-list';
import { View, Text, StyleSheet } from 'react-native';
type Item = {
id: string;
title: string;
};
const data: Item[] = [
{ id: '1', title: 'Item 1' },
{ id: '2', title: 'Item 2' },
{ id: '3', title: 'Item 3' },
// ... more items
];
function App() {
const [items, setItems] = useState(data);
const renderItem = ({ item, index }: RenderItemParams<Item>) => (
<View style={styles.item}>
<Text>{item.title}</Text>
</View>
);
return (
<DraggableList
data={items}
itemHeight={60}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onDragEnd={setItems}
style={styles.list}
/>
);
}
const styles = StyleSheet.create({
list: {
flex: 1,
},
item: {
height: 60,
backgroundColor: '#fff',
justifyContent: 'center',
paddingHorizontal: 16,
borderBottomWidth: 1,
borderBottomColor: '#eee',
},
});Nesting Multiple Draggable Lists
You can render multiple NestableDraggableFlatList components within a single scrollable parent using NestableScrollContainer. This is useful when you have multiple categories or sections that each need their own reorderable list.
Note: When using
NestableDraggableFlatList, React Native warnings about nested VirtualizedLists are automatically suppressed.
import {
NestableScrollContainer,
NestableDraggableFlatList,
} from 'react-native-reanimated-drag-list';
function App() {
const [data1, setData1] = useState(initialData1);
const [data2, setData2] = useState(initialData2);
const [data3, setData3] = useState(initialData3);
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text>{item.label}</Text>
</View>
);
const keyExtractor = (item) => item.id;
return (
<NestableScrollContainer style={styles.container}>
<Text style={styles.header}>Shopping List</Text>
<NestableDraggableFlatList
data={data1}
itemHeight={60}
renderItem={renderItem}
keyExtractor={keyExtractor}
onDragEnd={setData1}
/>
<Text style={styles.header}>Tasks</Text>
<NestableDraggableFlatList
data={data2}
itemHeight={60}
renderItem={renderItem}
keyExtractor={keyExtractor}
onDragEnd={setData2}
/>
<Text style={styles.header}>Favorites</Text>
<NestableDraggableFlatList
data={data3}
itemHeight={60}
renderItem={renderItem}
keyExtractor={keyExtractor}
onDragEnd={setData3}
/>
</NestableScrollContainer>
);
}Dynamic Item Heights
NestableDraggableFlatList supports items with variable heights. Simply omit the itemHeight prop and let items measure themselves:
import {
NestableScrollContainer,
NestableDraggableFlatList,
} from 'react-native-reanimated-drag-list';
function App() {
const [items, setItems] = useState(data);
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text style={styles.title}>{item.title}</Text>
{item.description && (
<Text style={styles.description}>{item.description}</Text>
)}
</View>
);
return (
<NestableScrollContainer style={styles.container}>
<NestableDraggableFlatList
data={items}
// No itemHeight - heights are measured automatically
estimatedItemHeight={80} // Optional: helps with initial layout
renderItem={renderItem}
keyExtractor={(item) => item.id}
onDragEnd={setItems}
/>
</NestableScrollContainer>
);
}Drag Disabled Zones
Use DragDisabledZone to wrap interactive elements (buttons, inputs, etc.) that should not trigger drag activation:
import {
NestableScrollContainer,
NestableDraggableFlatList,
DragDisabledZone,
} from 'react-native-reanimated-drag-list';
function App() {
const [items, setItems] = useState(data);
const renderItem = ({ item }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
<DragDisabledZone>
<TouchableOpacity
style={styles.deleteButton}
onPress={() => deleteItem(item.id)}
>
<Text>Delete</Text>
</TouchableOpacity>
</DragDisabledZone>
</View>
);
return (
<NestableScrollContainer>
<NestableDraggableFlatList
data={items}
itemHeight={60}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onDragEnd={setItems}
/>
</NestableScrollContainer>
);
}API Reference
DraggableList Props
| Prop | Type | Required | Default | Description |
| ------ | ------ | ---------- | --------- | ------------- |
| data | T[] | ✅ | - | Array of items to render |
| itemHeight | number | ✅ | - | Height of each item (must be consistent) |
| renderItem | (params: RenderItemParams<T>) => ReactNode | ✅ | - | Function to render each item |
| keyExtractor | (item: T) => string | ✅ | - | Function to extract unique key from item |
| onDragEnd | (data: T[]) => void | ✅ | - | Callback with reordered data after drag ends |
| style | ViewStyle | ❌ | - | Style for the ScrollView container |
| contentContainerStyle | ViewStyle | ❌ | - | Style for the content container |
| dragActivationDelay | number | ❌ | 200 | Milliseconds to hold before drag activates |
NestableScrollContainer Props
Extends all ScrollView props from react-native-gesture-handler. Supports ref forwarding to access the underlying ScrollView.
| Prop | Type | Required | Description |
| ---- | ---- | -------- | ----------- |
| children | ReactNode | ✅ | Content including NestableDraggableFlatList components |
| measureKey | number or string | ❌ | When this value changes, the container re-measures its position on screen. Useful when the container is inside an animated parent like a BottomSheet. |
| onScroll | ScrollViewProps['onScroll'] | ❌ | Scroll event handler |
| onScrollBeginDrag | ScrollViewProps['onScrollBeginDrag'] | ❌ | Called when user begins dragging the scroll view |
| onScrollEndDrag | ScrollViewProps['onScrollEndDrag'] | ❌ | Called when user stops dragging the scroll view |
| onMomentumScrollEnd | ScrollViewProps['onMomentumScrollEnd'] | ❌ | Called when momentum scroll animation ends |
Usage with BottomSheet
When using NestableScrollContainer inside a BottomSheet, pass the bottom sheet's index as measureKey to ensure auto-scroll works correctly when the sheet animates:
import BottomSheet from '@gorhom/bottom-sheet';
import { NestableScrollContainer, NestableDraggableFlatList } from 'react-native-reanimated-drag-list';
function MyComponent() {
const [bottomSheetIndex, setBottomSheetIndex] = useState(1);
return (
<BottomSheet
index={1}
snapPoints={['25%', '55%']}
onChange={setBottomSheetIndex}
>
<NestableScrollContainer measureKey={bottomSheetIndex}>
<NestableDraggableFlatList
data={data}
itemHeight={100}
renderItem={renderItem}
onDragEnd={handleDragEnd}
keyExtractor={(item) => item.id}
/>
</NestableScrollContainer>
</BottomSheet>
);
}NestableDraggableFlatList Props
| Prop | Type | Required | Default | Description |
| ---- | ---- | -------- | ------- | ----------- |
| data | T[] | ✅ | - | Array of items to render |
| itemHeight | number | ❌ | - | Fixed height for all items. If omitted, heights are measured dynamically. |
| estimatedItemHeight | number | ❌ | 60 | Estimated item height for initial layout (only used when itemHeight is not provided) |
| renderItem | (params: RenderItemParams<T>) => ReactNode | ✅ | - | Function to render each item |
| keyExtractor | (item: T) => string | ✅ | - | Function to extract unique key from item |
| onDragEnd | (data: T[]) => void | ✅ | - | Callback with reordered data after drag ends |
| style | ViewStyle | ❌ | - | Style for the list container |
| contentContainerStyle | ViewStyle | ❌ | - | Style for the content container |
| dragActivationDelay | number | ❌ | 200 | Milliseconds to hold before drag activates |
| ListHeaderComponent | ReactNode | ❌ | - | Component rendered above list items |
| ListFooterComponent | ReactNode | ❌ | - | Component rendered below list items |
| autoScrollThreshold | number | ❌ | 100 | Distance from edge (in px) to trigger auto-scroll |
| autoScrollMaxSpeed | number | ❌ | 12 | Maximum auto-scroll speed (px per frame) |
| autoScrollMinSpeed | number | ❌ | 1 | Minimum auto-scroll speed (px per frame) |
| autoScrollSmoothing | number | ❌ | 0.15 | Smoothing factor for velocity transitions (0-1). Lower = smoother. |
DragDisabledZone
A wrapper component that prevents drag activation on its children. Use this for buttons, inputs, or other interactive elements within draggable items.
import { DragDisabledZone } from 'react-native-reanimated-drag-list';
<DragDisabledZone>
<Button onPress={handlePress} title="Click me" />
</DragDisabledZone>RenderItemParams
type RenderItemParams<T> = {
item: T; // The item data
index: number; // Current index in the list
drag: () => void; // Function to initiate drag (for custom handles)
isActive: boolean; // Whether this item is being dragged
};How It Works
- Long press an item to activate drag mode (default 200ms)
- Drag the item to reorder - other items animate out of the way
- Release to drop the item in its new position
- Scroll normally with quick swipes - dragging only activates on hold
Auto-scroll Behavior
The list automatically scrolls when you drag an item near the top or bottom edges:
- Direction-aware: Only scrolls when you're actively moving toward the edge
- Progressive speed: Uses an exponential curve - gentle near the threshold, rapid at the edge
- Smooth integration: The dragged item follows the scroll seamlessly
License
MIT
