In this post I will demonstrate how we can use React-Natives Animated API and the Swipeable component from react-native-gesture-handler to create a FlatList that enables users to easily delete items by swiping and provides appropriate and tasteful visual feedback similar to Google's G-Mail app.
To start with, I've created a basic app that renders some example data inside a FlatList.
import { StatusBar } from 'expo-status-bar';
import { SafeAreaView } from 'react-native-safe-area-context';
import React, {useState} from 'react';
import { StyleSheet, Text, View, Animated, FlatList } from 'react-native';
import Swipeable from 'react-native-gesture-handler/Swipeable';
import data from './Exampledata'
function ListItem({email}){
const {title, sender, subject} = email
return(
<Animated.View style={{flex:1,flexDirection:'row', height:70, alignItems:'center',borderBottomWidth:1,backgroundColor:'white'}}>
<Text style={{width:150}}>{title}</Text>
<View style={{overflow:'visible'}}>
<Text>From: {sender}</Text>
<Text>Subject: {subject}</Text>
</View>
</Animated.View>
)
}
export default function App() {
const [emails, setEmails] = useState(data)
return (
<SafeAreaView style={styles.container}>
<FlatList
data={emails}
renderItem={({item}) =><ListItem email={item}/>}
/>
<StatusBar style="auto" />
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop:20,
marginHorizontal:10,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Its notable that the main component uses state to keep track of the emails to render and the individual list items are their own component.
So far, the app renders a list but we are not able to swipe yet. To be able to swipe our list items, we'll wrap the view with the Swipeable component and make a new function to pass to the 'renderRightActions' prop. The function we create needs to return some JSX which will be rendered when we swipe to the left on our list item. Here is the updated code for our ListItem component:
function ListItem({email}){
const {title, sender, subject} = email
const swipeRight = (progress,dragX) =>{
const scale = dragX.interpolate({
inputRange:[-200,0],
outputRange:[1,0.5],
extrapolate:'clamp'
})
return(
<Animated.View style={{backgroundColor:'red',width:"100%",justifyContent:'center'}}>
<Animated.Text style={{marginLeft:'auto',marginRight:50, fontSize:15, fontWeight:'bold',transform:[{scale}]}}>Delete Item</Animated.Text>
</Animated.View>
)
}
return(
<Swipeable renderRightActions={swipeRight} rightThreshold={-200}>
<Animated.View style={{flex:1,flexDirection:'row', height:70, alignItems:'center',borderBottomWidth:1,backgroundColor:'white'}}>
<Text style={{width:150}}>{title}</Text>
<View style={{overflow:'visible'}}>
<Text>From: {sender}</Text>
<Text>Subject: {subject}</Text>
</View>
</Animated.View>
</Swipeable>
)
}
Now, when we swipe left on our list item we'll see a red background with the Text 'Delete' appear behind.
There are a couple things to mention before moving on. I gave the View rendered by the renderRightActions prop a width of 100% because, similar to G-Mail, we want our List Item to appear like it is being pushed off to the side before we delete it. Also, I use the dragX variable to animate the text inside the Action View so that users have visual feedback for the drag distance at which an item will be deleted.
The next thing to do is make it so when our Action View is opened, the item shrinks and then gets deleted from state.
To shrink the list item we'll declare a new Animated Value, height, inside our ListItem component and make an animation that sets the height to 0. We'll also add height to the styles properties of the View that contains our List Item and the Action View.
To make the animation play after the Action View is opened, we just have to pass a function which plays our animation to the 'onSwipeableOpen' prop.
const height = new Animated.Value(70)
const animatedDelete=() => {
Animated.timing(height,{
toValue: 0,
duration: 350,
useNativeDriver:false
}).start()
}
<Swipeable renderRightActions={swipeRight} rightThreshold={-200} onSwipeableOpen={animatedDelete}>
One note about this animation is that you must have useNativeDriver set to false because the Native Driver does not support animating non-layout properties like height or width.
Finally, to delete the item from state after shrinking, we pass a callback to the start() method inside the function that plays the animation. To do that however, we have to pass the setState (in this case setEmails) function down to our ListItem component. Then we can call the setState function inside the ListItem component and delete the email. Here is the updated animation function.
const animatedDelete=() => {
Animated.timing(height,{
toValue: 0,
duration: 350,
useNativeDriver:false
}).start(() => setEmails(prevState => prevState.filter(e => e.id !== email.id)))
}
And that's it. Now we have a list that we can quickly and easily delete items from by swiping left. More than that, our list gracefully animates each deletion by shrinking the item that's being deleted so it doesn't looking jarring when our list rerenders without that item.
Top comments (1)