Search code examples
javascriptarraysreact-nativestatesplice

React native - remove object from array


I have a Modal that displays a list of contacts. onPress, contacts are dynamically added to a View. If a contact has already been added, on second onPress I would like to remove it from the View. For that I'm amending the state/array containing the contacts, using splice but it removes all the contacts at once.

I'm also trying to update the state of the 'Add' icon. If a contact has been added the Add icon Image should become the active one.

Not sure what I'm doing wrong?

Here's the Modal opened up:

enter image description here

My code:

    import React, {Component} from 'react'
    import {
        Text,
        View,
        ListView,
        ScrollView,
        StyleSheet,
        Image,
        TouchableHighlight,
        TextInput,
        Modal,
    } from 'react-native'


    const friends = new ListView.DataSource({
        rowHasChanged: (r1, r2) => r1 !== r2
    }).cloneWithRows([
        {
            id: 1,
            firstname: 'name1',
            surname: 'surname1',
            image: require('../images/friends/avatar-friend-01.png')
        },
        {
            id: 2,
            firstname: 'name2',
            surname: 'surname2',
            image: require('../images/friends/avatar-friend-02.png')
        },
        {
            id: 3,
            firstname: 'name3',
            surname: 'surname3',
            image: require('../images/friends/avatar-friend-03.png')
        },
    ])


    class AppView extends Component {
        state = {
            isModalVisible: false,
            contactsPicked: [],
            friendsState: {},
        }

        setModalVisible = visible => {
            this.setState({isModalVisible: visible})
        }

        pickContact = (friend) => {
            if(this.state.contactsPicked.indexOf(friend) < 0){
                var tempFriendsState = this.state.friendsState
                tempFriendsState[friend.id] = true

                this.setState({
                    contactsPicked: [ ...this.state.contactsPicked, friend],
                    friendsState: tempFriendsState,
                })
            }
            else{
                let index = this.state.contactsPicked.indexOf(friend)
                let nextContacts = this.state.contactsPicked
                nextContacts.splice(index,1)
                let tempFriendsState = this.state.friendsState
                tempFriendsState[friend.id] = false

                this.setState({
                    contactsPicked: nextContacts,
                    friendsState: tempFriendsState,
                })
            }
        }

        removeContact = (friend) => {
            let index = this.state.contactsPicked.indexOf(friend)
            let nextContacts = this.state.contactsPicked
            nextContacts.splice(index,1)

            this.setState({
                contactsPicked: nextContacts,
            })
        }

        _renderAddFriendTile = () => {
           return(
                <View style={[styles.step, styles.stepThree]}>
                    <View style={{flex:1}}>
                        <Text style={styles.heading}>Friend tile</Text>
                    </View>

                    {this.state.contactsPicked.length > 0 && (
                        <TouchableHighlight onPress={() => {this.removeContact(this.state.contactsPicked)}}>
                            <View>
                                {this.state.contactsPicked.map((contact,index) => (
                                    <View key={index} style={[styles.row, styles.friendRow]}>
                                        <Image source={contact.image} style={styles.friendIcon}></Image>
                                        <Text style={styles.name}>{contact.firstname} </Text>
                                        <Text style={styles.name}>{contact.surname}</Text>

                                        <View style={styles.roundIconContainer}>
                                            <View style={styles.roundIcon}>
                                                <View style={[styles.removeButton, styles.buttonSmall]}>
                                                    <Image source={require('../images/button-cross-small.png')} style={styles.crossIconSmall}></Image>
                                                </View>
                                            </View>
                                        </View>
                                    </View>
                                ))}
                            </View>
                        </TouchableHighlight>
                    )}

                    <TouchableHighlight style={styles.addFriendButtonContainer} onPress={() => {this.setModalVisible(true)}}>
                        <View style={styles.addFriendButton}>
                            <Text style={styles.addFriendButtonText}>Add friends</Text>
                        </View>
                    </TouchableHighlight>
                </View>
            )
        }

        render(){
            return (
                <ScrollView style={styles.container}>
                    <Modal
                        animationType={'fade'}
                        transparent={true}
                        visible={this.state.isModalVisible}
                    >
                        <View style={styles.addFriendModalContainer}>
                            <View style={styles.addFriendModal}>
                                <TouchableHighlight onPress={() => {this.setModalVisible(false)}}>
                                    <View>
                                        <Text>Close</Text>
                                    </View>
                                </TouchableHighlight>
                                <ListView
                                    dataSource={friends}
                                    renderRow={(friend) => {
                                        return (
                                            <FriendRow
                                                friend={friend}
                                                pickContact={this.pickContact}
                                                isSelected={this.state.friendsState[friend.id]}
                                            />
                                        )
                                    }}
                                />
                            </View>
                        </View>
                    </Modal>

                    {this._renderAddFriendTile()}
                </ScrollView>
            )
        }
    }

    class FriendRow extends Component {
        render(){
            return(
                <TouchableHighlight onPress={() => {this.props.pickContact(this.props.friend)}}>
                    <View style={[styles.row, styles.friendRow]}>
                        <Image source={this.props.friend.image} style={styles.friendIcon}></Image>
                        <Text style={styles.name}>{this.props.friend.firstname} </Text>
                        <Text style={styles.name}>{this.props.friend.surname}</Text>

                        <View style={styles.roundIconContainer}>
                            <View style={styles.roundIcon}>
                                <View style={this.props.isSelected ? [styles.buttonActive, styles.buttonSmall]: [styles.modalButtonInactive, styles.buttonSmall]}>
                                    <Image source={this.props.isSelected && require('../images/button-tick-small-on.png')} style={styles.buttonTickSmall}></Image>
                                </View>
                            </View>
                        </View>
                    </View>
                </TouchableHighlight>
            )
        }
    }

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#e1e1e1'
    },
    row: {
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'center',
    },
    step: {
        backgroundColor: '#ffffff',
        borderRadius: 4,
        borderLeftWidth: 5,
        flex: 1,
        marginLeft: 10,
        marginRight: 10,
        marginBottom: 10,
        paddingLeft: 15,
        paddingRight: 10,
        paddingTop: 15,
        paddingBottom: 20,
        shadowOffset: {
            width: 0,
            height: 2,
        },
        shadowRadius: 2,
        shadowOpacity: 0.2,
        shadowColor: '#000000',
    },
    stepThree: {
        borderLeftColor: '#ffbd18',
    },
    heading: {
        textAlign: 'center',
        fontWeight: 'bold',
        fontSize: 15,
        color: '#333333',
    },
    addFriendButtonContainer: {
        marginTop:15,
        flex:1,
        alignItems: 'center',
        justifyContent: 'center',
        flexDirection: 'row',
    },
    addFriendButton: {
        backgroundColor: '#ffbd18',
        width: 270,
        borderRadius: 4,
        paddingTop: 15,
        paddingBottom: 15,
    },
    addFriendButtonText: {
        color: '#ffffff',
        fontSize: 18,
        fontWeight: 'bold',
        textAlign: 'center',
    },
    pickContainer: {
        flex:1,
        flexDirection: 'row',
        justifyContent: 'space-between',
        alignItems: 'center',
        borderRightWidth: 1,
    },
    pickWrapper: {
        flex: 1,
        flexDirection: 'row',
        justifyContent: 'space-around',
        alignItems: 'center',
        marginTop: 10,
    },
    buttonBig: {
        height: 60,
        width: 60,
        borderRadius: 30,
    },
    buttonSmall: {
        height: 20,
        width: 20,
        borderRadius: 10,
    },
    buttonActive: {
        backgroundColor: '#fd6769',
        alignItems: 'center',
        justifyContent: 'center',
    },
    buttonInactive: {
        backgroundColor: '#eeeeee',
        alignItems: 'center',
        justifyContent: 'center',
    },
    removeButton:{
        backgroundColor: '#cccbcb',
        alignItems: 'center',
        justifyContent: 'center',
    },
    modalButtonInactive: {
        backgroundColor: '#ffffff',
        borderWidth: 1,
        borderColor: '#eeeeee',
    },
    buttonTickBig: {
        width: 34,
        height: 28,
    },
    buttonTickMinusBig: {
        width: 18,
        height: 8,
    },
    buttonTickSmall: {
        width: 12,
        height: 10,
    },
    crossIconSmall: {
        width: 12,
        height: 10,
    },
    pickText: {
        color: '#c7c7c7',
        fontWeight: 'bold',
    },
    addFriendModalContainer: {
        flex: 1,
    },
    addFriendModal: {
        flex: 1,
        backgroundColor: '#ffffff',
        borderRadius: 4,
        paddingLeft: 15,
        paddingRight: 10,
        paddingTop: 20,
        paddingBottom: 20,
        shadowOffset: {
            width: 0,
            height: 2,
        },
        shadowRadius: 2,
        shadowOpacity: 0.2,
        shadowColor: '#000000',
        textAlign: 'center',
    },
    nextButtonContainer: {
        marginBottom: 20,
    },
    nextButton: {
        textAlign:'right',
    },
    friendRow: {
        height: 60,
        borderBottomWidth: 1,
        borderBottomColor: '#eeeeee',
        justifyContent: 'flex-start',
    },
    friendIcon: {
        width: 50,
        height: 50,
        marginRight: 25,
    },
    roundIconContainer:{
        flex: 1,
        flexDirection: 'row',
        justifyContent: 'flex-end',
        alignItems: 'flex-end',
    },
    roundIcon: {
        height: 20,
        width: 20,
        borderRadius: 10,
        backgroundColor: '#fd6769',
        justifyContent: 'center',
        alignItems: 'center',
        marginRight: 20,
    },
})

export default AppView

Solution

  • 'using splice but it removes all the contacts at once.'

    Because you're using splice method wrong. Check the parameters the method gets. http://www.w3schools.com/jsref/jsref_splice.asp

    [ ...this.state.contactsPicked, this.state.contactsPicked.splice(friend)]

    this doesn't work as you expected as well. It merges two arrays.

    var parts = ['shoulders', 'knees'];
    var parts2 = ['shoulders'];
    var lyrics = [ ...parts, ...parts2 ];
    
    console.log(lyrics)

    It seems you don't have to use spread operator([...arr,.arr2]), you can simply do that

    1. Since it re creates subs every time modal's visiblity changes and even if you change state of parent component's state it does not re render subs so you need to keep internal state for you sub components
    2. Also your _renderAddFriendTile method was working wrong too. When you take a look at it carefully you will realize your mistake.
    3. Don't forget to change your icons with my test icons

      class AppView extends Component {
      constructor(props) {
          super(props);
          this.state = {
            isModalVisible: false,
            contactsPicked: [],
            friendsState: {},
          }
      }
    
        setModalVisible = visible => {
            this.setState({isModalVisible: visible})
        }
    
        pickContact = (friend) => {
            if(this.state.contactsPicked.indexOf(friend) < 0){
                var tempFriendsState = this.state.friendsState
                tempFriendsState[friend.id] = true
    
                this.setState({
                    contactsPicked: [ ...this.state.contactsPicked, friend],
                    friendsState: tempFriendsState,
                })
            }
            else{
                let index = this.state.contactsPicked.indexOf(friend)
                let nextContacts = this.state.contactsPicked
                nextContacts.splice(index,1)
                let tempFriendsState = this.state.friendsState
                tempFriendsState[friend.id] = false
    
                this.setState({
                    contactsPicked: nextContacts,
                    friendsState: tempFriendsState,
                })
            }
        }
    
        removeContact = (friend) => {
            let index = this.state.contactsPicked.indexOf(friend)
            let nextContacts = this.state.contactsPicked
            let tempFriendsState = this.state.friendsState
            tempFriendsState[friend.id] = false
            nextContacts.splice(index,1)
            console.log('removeContact'+friend.id);
            this.setState({
                contactsPicked: nextContacts,
                friendsState: tempFriendsState,
            })
        }
    
        _renderAddFriendTile = () => {
           return(
                <View style={[styles.step, styles.stepThree]}>
                    <View style={{flex:1}}>
                        <Text style={styles.heading}>Friend tile</Text>
                    </View>
    
                    { (this.state.contactsPicked.length) > 0 ?
                      this.state.contactsPicked.map((contact,index) => (
                                  <TouchableHighlight onPress={() => {this.removeContact(contact)}}>
                                      <View>
                                    <View key={index} style={[styles.row, styles.friendRow]}>
                                        <Image source={contact.image} style={styles.friendIcon}></Image>
                                        <Text style={styles.name}>{contact.firstname} </Text>
                                        <Text style={styles.name}>{contact.surname}</Text>
    
                                        <View style={styles.roundIconContainer}>
                                            <View style={styles.roundIcon}>
                                                <View style={[styles.removeButton, styles.buttonSmall]}>
                                                    <Image source={require('./images/redtree.jpg')} style={styles.crossIconSmall}></Image>
                                                </View>
                                            </View>
                                        </View>
                                    </View>
                                    </View>
                                </TouchableHighlight>
                                ))
                                : null
    
                    }
    
                    <TouchableHighlight style={styles.addFriendButtonContainer} onPress={() => {this.setModalVisible(true)}}>
                        <View style={styles.addFriendButton}>
                            <Text style={styles.addFriendButtonText}>Add friends</Text>
                        </View>
                    </TouchableHighlight>
                </View>
            )
        }
    
        render(){
            return (
                <ScrollView style={styles.container}>
                    <Modal
                        animationType={'fade'}
                        transparent={true}
                        visible={this.state.isModalVisible}
                    >
                        <View style={styles.addFriendModalContainer}>
                            <View style={styles.addFriendModal}>
                                <TouchableHighlight onPress={() => {this.setModalVisible(false)}}>
                                    <View>
                                        <Text>Close</Text>
                                    </View>
                                </TouchableHighlight>
                                <ListView
                                    dataSource={friends}
                                    renderRow={(friend) => {
                                        return (
                                            <FriendRow
                                                friend={friend}
                                                pickContact={this.pickContact}
                                                isSelected={this.state.friendsState[friend.id]}
                                            />
                                        )
                                    }}
                                />
                            </View>
                        </View>
                    </Modal>
    
                    {this._renderAddFriendTile()}
                </ScrollView>
            )
        }
    }
    
    class FriendRow extends Component {
      constructor(props) {
          super(props);
          this.state = {
           isSelected:this.props.isSelected,
          }
      }
    
      componentDidMount(){
        console.log('didmount');
      }
        render(){
            var imageSource = (this.state.isSelected==true) ?  require('./images/tree.jpg') : ''
            console.log('friend'+!this.props.isSelected)
            return(
                <TouchableHighlight onPress={() => {this.props.pickContact(this.props.friend);this.setState({isSelected:!this.state.isSelected})}}>
                    <View style={[styles.row, styles.friendRow]}>
                        <Image source={this.props.friend.image} style={styles.friendIcon}></Image>
                        <Text style={styles.name}>{this.props.friend.firstname} </Text>
                        <Text style={styles.name}>{this.props.friend.surname}</Text>
    
                        <View style={styles.roundIconContainer}>
                            <View style={styles.roundIcon}>
                                <View style={this.state.isSelected ? [styles.buttonActive, styles.buttonSmall]: [styles.modalButtonInactive, styles.buttonSmall]}>
                                    <Image source={imageSource} style={styles.buttonTickSmall}></Image>
                                </View>
                            </View>
                        </View>
                    </View>
                </TouchableHighlight>
            )
        }
    }