React native delete multiple items from state array

Viewed 2674

I have a directory which stores images taken using the camera. For saving images I am using RNFS. I am using react-native-photo-browser.

The gallery itself doesn't have any options to delete the items from the gallery. So I am working to achieve it

export default class GridGallery extends React.Component{

    static navigationOptions = {
      title: 'Image Gallery',
    };

    constructor(props) {
        super(props)
        this.state = {
          filesList : [],
          mediaSelected: [],
          base64URI: null,
          galleryList: []
        }
    }

    componentDidMount(){
        FileList.list((files) => {

            if(files != null) {

                this.fileUrl = files[0].path;

                files = files.sort((a, b) => {

                    if (a.ctime < b.ctime)
                        return 1;
                    if (a.ctime > b.ctime)
                        return -1;
                    return 0;
                });

                this.setState({
                    filesList: files
                });
            }
            console.warn(this.state.filesList);
            this.getFiles();
        });
      }

    getFiles(){
      //console.warn(this.state.filesList);

      const ArrFiles = this.state.filesList.map((file) =>
        ({ caption : file.name, photo : file.path })
      );
      //console.warn(ArrFiles);

      this.setState({ galleryList : ArrFiles });
    }

    onActionButton = (media, index) => {
        if (Platform.OS === 'ios') {
          ActionSheetIOS.showShareActionSheetWithOptions(
            {
              url: media.photo,
              message: media.caption,
            },
            () => {},
            () => {},
          );
        } else {
          alert(`handle sharing on android for ${media.photo}, index: ${index}`);
        }
      };

      handleSelection = async (media, index, isSelected) => {
        if (isSelected == true) {
            this.state.mediaSelected.push(media.photo);
        } else {
         this.state.mediaSelected.splice(this.state.mediaSelected.indexOf(media.photo), 1);
        }
         console.warn(this.state.mediaSelected);
      }

      deleteImageFile = () => {

        const dirPicutures = RNFS.DocumentDirectoryPath;
        //delete mulitple files
        console.warn(this.state.mediaSelected);
        this.state.mediaSelected.map((file) =>
        // filepath = `${dirPicutures}/${file}`
          RNFS.exists(`${file}`)
          .then( (result) => {
              console.warn("file exists: ", result);

              if(result){
                return RNFS.unlink(`${file}`)
                  .then(() => {
                    console.warn('FILE DELETED');
                    let tempgalleryList = this.state.galleryList.filter(item => item.photo !== file);
                    this.setState({ galleryList : tempgalleryList })
                  })
                  // `unlink` will throw an error, if the item to unlink does not exist
                  .catch((err) => {
                    console.warn(err.message);
                  });
              }

            })
            .catch((err) => {
              console.warn(err.message);
            })
        )

     }

     renderDelete(){
       const { galleryList } = this.state;
       if(galleryList.length>0){
         return(
           <View style={styles.topRightContainer}>
           <TouchableOpacity style={{alignItems: 'center',right: 10}} onPress={this.deleteImageFile}>
             <Image
               style={{width: 24, height: 24}}
               source={require('../assets/images/ic_delete.png')}
             />
             </TouchableOpacity>
           </View>
         )
       }
     }

     goBack() {
       const { navigation } = this.props;
       navigation.pop;
     }

    render() {
      const { galleryList } = this.state;
        return (
            <View style={styles.container}>
                <View style={{flex: 1}}>
                <PhotoBrowser
                    mediaList={galleryList}
                    enableGrid={true}
                    displayNavArrows={true}
                    displaySelectionButtons={true}
                    displayActionButton={true}
                    onActionButton={this.onActionButton}
                    displayTopBar = {true}
                    onSelectionChanged={this.handleSelection}
                    startOnGrid={true}
                    initialIndex={0}
                />
                </View>
                {this.renderDelete()}
            </View>
        )
    }
}

An example list of images:

[
    {
     photo:'4072710001_f36316ddc7_b.jpg',
      caption: 'Grotto of the Madonna',
      },
      {
        photo: /media/broadchurch_thumbnail.png,
        caption: 'Broadchurch Scene',
      },
      {
        photo:
          '4052876281_6e068ac860_b.jpg',
          caption: 'Beautiful Eyes',
      },
  ]

My aim is whenever the item from state galleryList is removed I need to refresh the component, so the deleted image will be removed from the gallery. So When I try to use filter the galleryList it deleting other images instead of other images:

let tempgalleryList = this.state.galleryList.filter(item => item.photo !== file);
                    this.setState({ galleryList : tempgalleryList })

MCVE -> This is a minified version of my code, you can see the images are deleting randomly

1 Answers

Problem

let tempgalleryList = this.state.galleryList.filter(item => item.photo !== file);
this.setState({ galleryList : tempgalleryList })

Since setState is async, this.state.galleryList will not be updated in each iteration of your map function, so the final updated state will only have one item filtered out instead of all selected items.

Solution

You can use the callback version of setState which uses the updated state instead:

this.setState(prevState => ({ 
  galleryList : prevState.galleryList.filter(item => item.photo !== file), 
}));

Alternative solution

Instead of calling setState in every iteration, you can call it outside of your map function instead (though setState updates will be batched anyway so no significant performance improvement):

this.setState(prevState => ({ 
  galleryList : prevState.galleryList.filter(item => !prevState.mediaSelected.includes(item.photo)), 
}));

Other problems with your code

  this.state.mediaSelected.push(media.photo); 
} else {
  this.state.mediaSelected.splice(this.state.mediaSelected.indexOf(media.photo), 1);

You are directly mutating your state here. Do this instead:

this.setState(prevState => ({ 
  mediaSelected: prevState.mediaSelected.concat(media.photo) 
})); 

this.setState(prevState => ({ 
  mediaSelected: prevState.mediaSelected.filter(e => e != media.photo) 
})); 
Related