@souscheflabs/reanimated-flashlist
v0.5.5
Published
A high-performance animated FlashList with drag-to-reorder and entry/exit animations (New Architecture)
Downloads
92
Maintainers
Readme
@souscheflabs/reanimated-flashlist
A high-performance animated FlashList with drag-to-reorder and entry/exit animations for React Native.
Features
- Drag-to-reorder with smooth animations and autoscroll
- Entry animations when items appear in the list
- Exit animations with configurable slide/fade/scale effects
- Layout animations for remaining items when one is removed
- 60fps performance via Reanimated UI-thread animations
- Full TypeScript support with generics
Installation
From npm (when published)
npm install @souscheflabs/reanimated-flashlistFrom GitHub (private repo)
Using HTTPS (will prompt for credentials):
npm install github:SousChefLabs/reanimated-flashlist#v0.1.1Using SSH (recommended for CI/private repos):
npm install git+ssh://[email protected]:SousChefLabs/reanimated-flashlist.git#v0.1.1Or add to package.json:
{
"dependencies": {
"@souscheflabs/reanimated-flashlist": "github:SousChefLabs/reanimated-flashlist#v0.1.1"
}
}Replace v0.1.1 with the desired version tag.
Requirements
React Native New Architecture required. This library uses TurboModules and JSI for optimal performance.
- React Native 0.74.0+ with New Architecture enabled
@shopify/flash-listv2.0.0+react-native-reanimatedv4.0.0+react-native-gesture-handlerv2.14.0+
npm install @shopify/flash-list react-native-reanimated react-native-gesture-handlerQuick Start
import { AnimatedFlashList, type AnimatedListItem } from '@souscheflabs/reanimated-flashlist';
import { GestureDetector } from 'react-native-gesture-handler';
interface MyItem extends AnimatedListItem {
title: string;
}
function MyList() {
const [items, setItems] = useState<MyItem[]>([
{ id: '1', title: 'Item 1' },
{ id: '2', title: 'Item 2' },
]);
return (
<AnimatedFlashList<MyItem>
data={items}
keyExtractor={(item) => item.id}
renderItem={({ item, dragHandleProps, triggerExitAnimation }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
{/* Drag handle */}
{dragHandleProps && (
<GestureDetector gesture={dragHandleProps.gesture}>
<View style={styles.dragHandle}>
<DragIcon />
</View>
</GestureDetector>
)}
{/* Delete button with exit animation */}
<TouchableOpacity
onPress={() => {
triggerExitAnimation(1, () => {
setItems(prev => prev.filter(i => i.id !== item.id));
}, 'fast');
}}
>
<DeleteIcon />
</TouchableOpacity>
</View>
)}
dragEnabled
onReorder={(itemId, fromIndex, toIndex) => {
setItems(prev => {
const newItems = [...prev];
const [removed] = newItems.splice(fromIndex, 1);
newItems.splice(toIndex, 0, removed);
return newItems;
});
}}
/>
);
}API Reference
AnimatedFlashListProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| data | T[] | Required | Array of items (must extend AnimatedListItem) |
| keyExtractor | (item: T, index: number) => string | Required | Extract unique key for each item |
| renderItem | (info: AnimatedRenderItemInfo<T>) => ReactElement | Required | Render function for items |
| dragEnabled | boolean | false | Enable drag-to-reorder |
| onReorder | (itemId, fromIndex, toIndex) => void | - | Called when item is reordered |
| onReorderByNeighbors | (itemId, afterId, beforeId) => void | - | Alternative callback with neighbor IDs (for fractional indexing) |
| canDrag | (item: T, index: number) => boolean | () => true | Control which items can be dragged |
| onHapticFeedback | (type: HapticFeedbackType) => void | - | Haptic feedback callback |
| config | AnimatedFlashListConfig | - | Configuration overrides |
AnimatedRenderItemInfo
Props passed to your renderItem function:
| Prop | Type | Description |
|------|------|-------------|
| item | T | The item data |
| index | number | Current index |
| totalItems | number | Total items in list |
| dragHandleProps | { gesture, isDragging } \| null | Spread onto GestureDetector for drag handle |
| isDragEnabled | boolean | Whether drag is enabled for this item |
| triggerExitAnimation | (direction, onComplete, preset?) => void | Trigger exit animation |
| resetExitAnimation | () => void | Reset exit animation state |
Recipes
Checkbox Toggle with Exit Animation
When a checkbox is toggled and the item should exit:
renderItem={({ item, triggerExitAnimation }) => (
<View style={styles.item}>
<Checkbox
checked={item.completed}
onToggle={() => {
// Trigger exit animation, then remove item
triggerExitAnimation(1, () => {
removeItem(item.id);
}, 'fast'); // 'fast' preset for quick actions
}}
/>
<Text>{item.title}</Text>
</View>
)}Swipe to Delete
renderItem={({ item, triggerExitAnimation }) => (
<Swipeable
onSwipeRight={() => {
triggerExitAnimation(1, () => deleteItem(item.id));
}}
onSwipeLeft={() => {
triggerExitAnimation(-1, () => deleteItem(item.id));
}}
>
<ItemContent item={item} />
</Swipeable>
)}Custom Drag Handle
renderItem={({ item, dragHandleProps }) => (
<View style={styles.item}>
<Text>{item.title}</Text>
{dragHandleProps && (
<GestureDetector gesture={dragHandleProps.gesture}>
<Animated.View
style={[
styles.dragHandle,
// Optional: visual feedback during drag
useAnimatedStyle(() => ({
opacity: dragHandleProps.isDragging.value ? 0.5 : 1,
})),
]}
>
<GripIcon />
</Animated.View>
</GestureDetector>
)}
</View>
)}Configuration
Drag Configuration
<AnimatedFlashList
config={{
drag: {
itemHeight: 80, // Item height for drag calculations
dragScale: 1.05, // Scale when dragging
longPressDuration: 200, // ms to activate drag
edgeThreshold: 80, // px from edge to trigger autoscroll
maxScrollSpeed: 10, // Autoscroll speed
dropBounceScale: 0.97, // Scale for drop "plop" effect
dropHapticEnabled: true, // Enable drop haptic feedback
},
}}
/>Animation Presets
Exit animations have two presets:
'default'(300ms): Standard exit with staggered fade/scale'fast'(200ms): Quick exit for checkbox toggles
// Use fast preset for quick actions
triggerExitAnimation(1, onComplete, 'fast');
// Use default preset for deliberate actions
triggerExitAnimation(-1, onComplete, 'default');Layout Animations
When an item exits the list, remaining items automatically animate to fill the gap. This uses React Native's LayoutAnimation API and is enabled by default.
The animation is triggered automatically when triggerExitAnimation is called - no additional setup required.
Performance Tips
Enable Reanimated Feature Flags
For optimal animation performance, enable Reanimated's static feature flags. These reduce commit hook overhead and enable platform-specific fast paths for style updates.
Requirements: React Native 0.80+ and Reanimated 4.2.0+
Create or update react-native.config.js in your project root:
module.exports = {
reanimated: {
staticFeatureFlags: {
// Reduces commit hook overhead by limiting when hooks fire
USE_COMMIT_HOOK_ONLY_FOR_REACT_COMMITS: true,
// Fast path for non-layout style updates (opacity, transform)
ANDROID_SYNCHRONOUSLY_UPDATE_UI_PROPS: true,
IOS_SYNCHRONOUSLY_UPDATE_UI_PROPS: true,
},
},
};Add to your babel.config.js:
module.exports = {
plugins: [
[
'react-native-reanimated/plugin',
{
enableCppPropsIteratorSetter: true, // Faster C++ prop iteration
},
],
],
};Note: These flags require a clean rebuild. Run
npx react-native start --reset-cacheafter changing config.
Enable React Compiler
For optimal performance in your app, enable React Compiler (React 19+). It automatically memoizes components and callbacks, reducing re-renders.
npm install babel-plugin-react-compiler eslint-plugin-react-compiler --save-devAdd to your babel.config.js:
module.exports = {
plugins: [
['babel-plugin-react-compiler', {}],
],
};And to your ESLint config:
// eslint.config.js (flat config)
import reactCompiler from 'eslint-plugin-react-compiler';
export default [
{
plugins: { 'react-compiler': reactCompiler },
rules: { 'react-compiler/react-compiler': 'warn' },
},
];Memoize renderItem and keyExtractor
Always wrap renderItem and keyExtractor with useCallback to prevent unnecessary re-renders:
const keyExtractor = useCallback((item: MyItem) => item.id, []);
const renderItem = useCallback(
({ item, dragHandleProps }: AnimatedRenderItemInfo<MyItem>) => (
<MyItemComponent item={item} dragHandleProps={dragHandleProps} />
),
[]
);Wrap Item Components with React.memo
const MyItemComponent = React.memo(({ item, dragHandleProps }) => {
// ...
});Advanced Usage
Using Individual Hooks
For custom implementations, you can use the hooks directly:
import {
useDragGesture,
useDragShift,
useDragAnimatedStyle,
useListExitAnimation,
useListEntryAnimation,
} from '@souscheflabs/reanimated-flashlist';Context Providers
For building custom list implementations:
import {
DragStateProvider,
ListAnimationProvider,
useDragState,
useListAnimation,
} from '@souscheflabs/reanimated-flashlist';License
MIT
