Skip to main content

Reorderable List

Demonstrates a sortable list using DraxList — the simplest way to add drag-to-reorder.

Key Concepts

  • DraxList — wraps FlatList with full reorder support
  • Long-press to drag — 250ms default delay
  • Auto-scroll — scrolls when dragging near edges
  • Animation presets — items shift with smooth animations

Features Shown

FeatureHow
DraxListConvenience wrapper with data, keyExtractor, onReorder
animationConfigAnimation preset for shift animations
onReorderCallback with new data order
lockToMainAxisItems stay on the vertical axis during drag

Source Code

▶️ Live demo · 💻 Source

example/screens/reorderable-list.tsx
import { useState } from 'react';
import { StyleSheet, View, Text, Pressable } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { DraxProvider, DraxList } from 'react-native-drax';
import type { SortableAnimationPreset } from 'react-native-drax';

const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');

const PRESETS: { key: SortableAnimationPreset; label: string }[] = [
{ key: 'default', label: 'Default' },
{ key: 'spring', label: 'Spring' },
{ key: 'gentle', label: 'Gentle' },
{ key: 'snappy', label: 'Snappy' },
{ key: 'none', label: 'None' },
];

const getBackgroundColor = (alphaIndex: number) => {
switch (alphaIndex % 6) {
case 0:
return '#ffaaaa';
case 1:
return '#aaffaa';
case 2:
return '#aaaaff';
case 3:
return '#ffffaa';
case 4:
return '#ffaaff';
case 5:
return '#aaffff';
default:
return '#aaaaaa';
}
};

const getHeight = (alphaIndex: number) => {
let height = 50;
if (alphaIndex % 2 === 0) {
height += 10;
}
if (alphaIndex % 3 === 0) {
height += 20;
}
return height;
};

const getItemStyleTweaks = (alphaItem: string) => {
const alphaIndex = alphabet.indexOf(alphaItem);
return {
backgroundColor: getBackgroundColor(alphaIndex),
height: getHeight(alphaIndex),
};
};

export default function ReorderableList() {
const [alphaData, setAlphaData] = useState(alphabet);
const [animPreset, setAnimPreset] =
useState<SortableAnimationPreset>('default');
const insets = useSafeAreaInsets();

return (
<DraxProvider>
<View
testID="reorderable-list-screen"
style={[
styles.container,
{
paddingTop: insets.top,
paddingLeft: insets.left,
paddingRight: insets.right,
},
]}
>
<View style={styles.presetBar}>
<Text style={styles.presetLabel}>Animation:</Text>
{PRESETS.map((p) => (
<Pressable
key={p.key}
testID={`preset-${p.key}`}
onPress={() => setAnimPreset(p.key)}
style={[
styles.presetButton,
animPreset === p.key && styles.presetButtonActive,
]}
>
<Text
style={[
styles.presetButtonText,
animPreset === p.key && styles.presetButtonTextActive,
]}
>
{p.label}
</Text>
</Pressable>
))}
</View>
<DraxList
data={alphaData}
keyExtractor={(item) => item}
animationConfig={animPreset}
inactiveItemStyle={{ opacity: 0.5 }}
containerStyle={styles.container}
style={styles.list}
containerDraxViewProps={{
testID: 'sortable-list-container',
accessibilityLabel: 'Reorderable list of letters A through Z',
}}
scrollEventThrottle={16}
onReorder={({ data, fromIndex, fromItem, toIndex, toItem }) => {
console.log(
`[reorderableList:onReorder] from=${fromIndex} (${fromItem}) to=${toIndex} (${toItem})`
);
setAlphaData(data);
}}
onDragStart={({ index, item }) => {
console.log(
`[reorderableList:onDragStart] index=${index} item=${item}`
);
}}
onDragPositionChange={({
index,
item,
toIndex,
previousIndex,
}) => {
console.log(
`[reorderableList:onDragPositionChange] index=${index} item=${item} toIndex=${toIndex} previousIndex=${previousIndex}`
);
}}
onDragEnd={({ index, item, toIndex, cancelled }) => {
console.log(
`[reorderableList:onDragEnd] index=${index} item=${item} toIndex=${toIndex} cancelled=${cancelled}`
);
}}
renderItem={({ item }) => (
<View
testID={`sortable-item-${item}`}
style={[styles.alphaItem, getItemStyleTweaks(item)]}
>
<Text style={styles.alphaText}>{item}</Text>
</View>
)}
/>
</View>
</DraxProvider>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
},
list: {
flex: 1,
},
presetBar: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
paddingVertical: 6,
backgroundColor: '#f0f0f0',
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: '#ddd',
},
presetLabel: {
fontSize: 13,
fontWeight: '600',
color: '#555',
marginRight: 6,
},
presetButton: {
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 14,
backgroundColor: '#e0e0e0',
marginHorizontal: 3,
},
presetButtonActive: {
backgroundColor: '#3b82f6',
},
presetButtonText: {
fontSize: 12,
fontWeight: '500',
color: '#555',
},
presetButtonTextActive: {
color: '#fff',
},
alphaItem: {
backgroundColor: '#aaaaff',
borderRadius: 8,
margin: 4,
padding: 4,
justifyContent: 'center',
alignItems: 'center',
},
alphaText: {
fontSize: 28,
},
});