Hello guys, After a bit long period I come back with very interesting tutorial. If you guys remember, Some time ago, I have posted a tutorial “React Native dynamically Add View Component on Button Click with Animation Tutorial“. As the name indicates, in this tutorial I demonstrated, how we can add a View component on button click with nice animation. Many of my visitors asked about how we can add delete item functionality with nice smooth animation. So then I decided to write a complete new blog and through this tutorial, I am going to explain how we can Dynamically Add Remove Component with Animation, describing all functionalities(Add / Remove) and ya thanks for reading my blogs.
In brief, what I am doing in Dynamically Add / Remove component with Animation tutorial?
- We have a floating button, which allows you to add a View component dynamically with nice smooth animation.
- And each item have a Remove button which allows you to remove the selected item with animation. You can see the final output in the below GIF image.
So, let’s get started…
Step – 1: Import all required components from react , react-native packages and get the device width through react native Dimensions API.
import React, { Component } from 'react'; import { View, Text, Platform, StyleSheet, TouchableOpacity, Animated, ScrollView, Image, Dimensions, LayoutAnimation, UIManager } from 'react-native'; const width = Dimensions.get('window').width;
Implement Code for Item (Child) Component:
Step – 2: Implement constructor method.
constructor() { super(); this.animatedValue = new Animated.Value(0); if( Platform.OS === 'android' ) { UIManager.setLayoutAnimationEnabledExperimental(true); } }
Explanation:
- I am using react native LayoutAnimation API to animate the remaining ScrollView items after removing the selected item and LayoutAnimation is not working by default on Android. So, we have to enable it for Android devices. That’s why I am using UIManager.setLayoutAnimationEnabledExperimental(true); statement.
Step – 3: Implement shouldComponentUpdate method.
shouldComponentUpdate(nextProps, nextState) { if(nextProps.item.id !== this.props.item.id) { return true; } return false; }
Explanation:
- Basically, shouldComponentUpdate is a lifecycle method of react and used to improve the performance of the app.
- shouldComponentUpdate method returns a boolean either true or false . If it returns true then component re-renders otherwise react skips the re-rendering process.
- In our case, every time when we add a new component then rendering occurs for all existing items. Suppose, we have 9 items in ScrollView then after adding 10th item, Item component re-renders for all existing 9 items and this is wasteful re-rendering and harms our app’s performance. So, to prevent the wasteful re-rendering shouldComponentUpdate method is used.
Step – 4: Implement componentDidMount method.
componentDidMount() { Animated.timing( this.animatedValue, { toValue: 0.5, duration: 500, useNativeDriver: true } ).start(() => { this.props.afterAnimationComplete(); }); }
Explanation:
- This is the place where our animation will work for adding a component and after completing the animation I am calling afterAnimationComplete method which is received as prop from parent component.
Step – 5: Implement removeItem method.
removeItem = () => { Animated.timing( this.animatedValue, { toValue: 1, duration: 500, useNativeDriver: true } ).start(() => { this.props.removeItem(this.props.item.id); }); }
Explanation:
- This method is responsible to execute the removing animation and again I am calling removeItem method to actually remove the selected item from ScrollView and this method is also received as prop from parent component.
Step – 6: Implement render method.
render() { const translateAnimation = this.animatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [-width, 0, width] }); const opacityAnimation = this.animatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0, 1, 0] }); return ( <Animated.View style={[ styles.viewHolder, { transform: [{ translateX: translateAnimation}], opacity: opacityAnimation }]} > <Text style={styles.text}> Row {this.props.item.text} </Text> <TouchableOpacity style={styles.removeBtn} onPress={this.removeItem} > <Image source={require('./assets/images/add.png')} style={styles.removeIcon} /> </TouchableOpacity> </Animated.View> ); }
Explanation:
- translateAnimation is responsible for moving the component with animation with opacity by using opacityAnimation .
- First, when we add a new component then element’s translateX position is equal to the -width and component animates from -width to 0 as you can see in the translateAnimation ‘s outputRange and when user clicks on the remove icon on any component then component animates from 0 to width (as you can see in the translateAnimation ‘s outputRange) and opacityAnimation also works along with translateAnimation .
Implement Code for App (Parent) Component:
Step – 7: Implement constructor method.
constructor() { super(); this.state = { valueArray: [], disabled: false } this.addNewEle = false; this.index = 0; }
Step – 8: Implement afterAnimationComplete method.
afterAnimationComplete = () => { this.index += 1; this.setState({ disabled: false }); }
Explanation:
- Remember, this method is called by Item component after completing the adding component animation.
- In this method, I am going to set false the disabled state, which re-enables the add component (+) button and allows to add new component.
Step – 9: Implement addMore method.
addMore = () => { this.addNewEle = true; const newlyAddedValue = { id: "id_" + this.index, text: this.index + 1 }; this.setState({ disabled: true, valueArray: [...this.state.valueArray, newlyAddedValue] }); }
Explanation:
- This method is responsible to add a new object ( will define new component ) into array and disable the add button while running adding component animation.
Step – 10: Implement remove method.
remove(id) { this.addNewEle = false; const newArray = [...this.state.valueArray]; newArray.splice(newArray.findIndex(ele => ele.id === id), 1); this.setState(() => { return { valueArray: newArray } }, () => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); }); }
Explanation:
- In this method, I am removing the selected item from array and applying the LayoutAnimation to re-arrange the remaining components with nice animation.
Step – 11: Implement render method.
render() { return ( <View style={styles.container}> <ScrollView ref={scrollView => this.scrollView = scrollView} onContentSizeChange={()=> { this.addNewEle && this.scrollView.scrollToEnd(); }} > <View style={{ flex: 1, padding: 4 }}> {this.state.valueArray.map(ele => { return ( <Item key={ele.id} item={ele} removeItem={(id) => this.remove(id)} afterAnimationComplete={this.afterAnimationComplete} /> ) })} </View> </ScrollView> <TouchableOpacity activeOpacity={0.8} style={styles.btn} disabled={this.state.disabled} onPress={this.addMore} > <Image source = { require('./assets/images/add.png') } style = { styles.btnImage }/> </TouchableOpacity> </View> ); }
Step – 12: Apply custom styles for all required components.
const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#eee', justifyContent: 'center', paddingTop: (Platform.OS == 'ios') ? 20 : 0 }, viewHolder: { paddingVertical: 15, backgroundColor: '#B00020', justifyContent: 'center', alignItems: 'flex-start', margin: 4, paddingLeft: 15, borderRadius: 10 }, text: { color: 'white', fontSize: 25, paddingRight: 17 }, btn: { position: 'absolute', right: 25, bottom: 25, borderRadius: 30, width: 60, height: 60, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.7)', padding: 15 }, btnImage: { resizeMode: 'contain', width: '100%', tintColor: 'white' }, removeBtn: { position: 'absolute', right: 13, width: 25, height: 25, borderRadius: 15, padding: 7, justifyContent: 'center', alignItems: 'center', backgroundColor: 'white' }, removeIcon: { width: '100%', transform: [{ rotate: '45deg' }], resizeMode: 'contain' } });
Complete Source Code:
import React, { Component } from 'react'; import { View, Text, Platform, StyleSheet, TouchableOpacity, Animated, ScrollView, Image, Dimensions, LayoutAnimation, UIManager } from 'react-native'; const width = Dimensions.get('window').width; class Item extends Component { constructor() { super(); this.animatedValue = new Animated.Value(0); if( Platform.OS === 'android' ) { UIManager.setLayoutAnimationEnabledExperimental(true); } } shouldComponentUpdate(nextProps, nextState) { if(nextProps.item.id !== this.props.item.id) { return true; } return false; } componentDidMount() { Animated.timing( this.animatedValue, { toValue: 0.5, duration: 500, useNativeDriver: true } ).start(() => { this.props.afterAnimationComplete(); }); } removeItem = () => { Animated.timing( this.animatedValue, { toValue: 1, duration: 500, useNativeDriver: true } ).start(() => { this.props.removeItem(this.props.item.id); }); } render() { const translateAnimation = this.animatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [-width, 0, width] }); const opacityAnimation = this.animatedValue.interpolate({ inputRange: [0, 0.5, 1], outputRange: [0, 1, 0] }); return ( <Animated.View style={[ styles.viewHolder, { transform: [{ translateX: translateAnimation}], opacity: opacityAnimation }]} > <Text style={styles.text}> Row {this.props.item.text} </Text> <TouchableOpacity style={styles.removeBtn} onPress={this.removeItem} > <Image source={require('./assets/images/add.png')} style={styles.removeIcon} /> </TouchableOpacity> </Animated.View> ); } } export default class App extends Component { constructor() { super(); this.state = { valueArray: [], disabled: false } this.addNewEle = false; this.index = 0; } afterAnimationComplete = () => { this.index += 1; this.setState({ disabled: false }); } addMore = () => { this.addNewEle = true; const newlyAddedValue = { id: "id_" + this.index, text: this.index + 1 }; this.setState({ disabled: true, valueArray: [...this.state.valueArray, newlyAddedValue] }); } remove(id) { this.addNewEle = false; const newArray = [...this.state.valueArray]; newArray.splice(newArray.findIndex(ele => ele.id === id), 1); this.setState(() => { return { valueArray: newArray } }, () => { LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut); }); } render() { return ( <View style={styles.container}> <ScrollView ref={scrollView => this.scrollView = scrollView} onContentSizeChange={()=> { this.addNewEle && this.scrollView.scrollToEnd(); }} > <View style={{ flex: 1, padding: 4 }}> {this.state.valueArray.map(ele => { return ( <Item key={ele.id} item={ele} removeItem={(id) => this.remove(id)} afterAnimationComplete={this.afterAnimationComplete} /> ) })} </View> </ScrollView> <TouchableOpacity activeOpacity={0.8} style={styles.btn} disabled={this.state.disabled} onPress={this.addMore} > <Image source = { require('./assets/images/add.png') } style = { styles.btnImage }/> </TouchableOpacity> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#eee', justifyContent: 'center', paddingTop: (Platform.OS == 'ios') ? 20 : 0 }, viewHolder: { paddingVertical: 15, backgroundColor: '#B00020', justifyContent: 'center', alignItems: 'flex-start', margin: 4, paddingLeft: 15, borderRadius: 10 }, text: { color: 'white', fontSize: 25, paddingRight: 17 }, btn: { position: 'absolute', right: 25, bottom: 25, borderRadius: 30, width: 60, height: 60, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.7)', padding: 15 }, btnImage: { resizeMode: 'contain', width: '100%', tintColor: 'white' }, removeBtn: { position: 'absolute', right: 13, width: 25, height: 25, borderRadius: 15, padding: 7, justifyContent: 'center', alignItems: 'center', backgroundColor: 'white' }, removeIcon: { width: '100%', transform: [{ rotate: '45deg' }], resizeMode: 'contain' } });
Enjoy guys and Happy Coding…
how to genric button add delet button like as a pluse button also add minus button genric
in left side click to remove last item view in row
Hi nilesh, I didn’t get your question properly. Can you please elaborate?
hello sir you are posted very good tutorials on react native for begineers,i appreciates you very much.
sir iam searching over the internet but none did project on FETCHING MARKERS FROM DATABASE USING MYSQL+PHP ,
i have source code and php connections and mysql databse
Getting error like undefined is not an object(evaluating ‘nextProps.item.id’)
Hi. stackmary can you share your code snippet. It would help me to find out the issue in a better way.
Thank you very much I finished my college assignment from this site.
Your welcome Goutham Reddy 🙂