React native delete multiple items from state array - reactjs

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

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

RefrenceError: Can't find variable in react native

I tried to access all the files in a custom folder I created on my RNCamera roll app to create a gallery with it. In my code I believe i specified the variable "videos", but still am getting a reference error: "can't find variable videos", what do i do to solve it, how will i be able to get rid of the error.... here is my code...
I added constructor in the this.state but still get the same error
constructor() {
super();
this.state = {
modalVisible: false,
videos: [],
index: null
}
}
getPhotos = () => {
CameraRoll.getPhotos({
first: 20,
groupTypes: 'Album',
groupName: 'Custom VideoFolder',
assetType: 'Videos'
})
.then(r => this.setState({ videos: r.edges}))
.then((statResult) => {
let videos = []
var allowedExtensions = /(\.avi|\.mp4|\.mov|\.wmv|\.avi)$/i;
statResult.forEach(item => {
if (item.isFile() && !allowedExtensions.exec(item.originalFilepath)) {
videos.push(item)
}
});
console.log(videos)
})
}
toggleModal = () => {
this.setState({ modalVisible: !this.state.modalVisible})
}
share = () => {
const vocvideo = this.state.videos[this.state.index].node.video.uri
RNFetchBlob.fs.readFile(vocvideo, 'uri')
.then((data) => {
let shareOptions = {
title: "React Native Share Example",
message: "Check out this video!",
url: `data:video/mp4;uri,${data}`,
subject: "Check out this video!"
};
Share.open(shareOptions)
.then((res) => console.log('res:', res))
.catch(err => console.log('err', err))
})
}
render() {
console.log('state :', this.state)
return (
<View style={styles.container}>
<Button
title='View videos'
onPress={() => { this.toggleModal(); this.getPhotos() }}
/>
<Modal
animationType={"slide"}
transparent={false}
visible={this.state.modalVisible}
onRequestClose={() => console.log('closed')}
>
<View style={styles.modalContainer}>
<Button
title='Close'
onPress={this.toggleModal}
/>
<ScrollView
contentContainerStyle={styles.scrollView}>
{
this.state.videos.map((p, i) => {
const isSelected = i === this.state.index;
const divide = isSelected && this.share === true ? 1 : 3;
return (
<Video
style={{opacity: i === this.state.index ? 0.5 : 1, width: width/divide, height: width/divide}}
key={i}
underlayColor='transparent'
onPress={() => this.setIndex(i)}
source={{uri: video}}
/>
)
})
}
</ScrollView>

React state is not updated immediately after deleting data

I have a problem to update the view in React Native after deleting a POST.
I think it could be a problem with the "state" but don't know how to fix it.
This is my list of Posts.
When I press on an item, it ask us to confirm the action.
When we confirm the action of delete, POST is deleted from Firebase but the view is not updated (Still 2 items in the list but only one in database. if I refresh and re-enter to the component, the list is updated)
This is my code :
class GetPosts extends React.Component {
static navigationOptions = ({navigation}) => {
const {params} = navigation.state;
};
constructor(props) {
super(props);
this.state = {
data: {},
data2: [],
posts: {},
newArray: [],
postsCount: 0,
};
}
componentDidMount() {
var f_id = this.props.identifier;
firebase
.database()
.ref('/posts/')
.orderByKey()
.on('value', snapshot => {
snapshot.forEach(el => {
if (el.val().film_id == f_id) {
this.state.data = [
{
email: el.val().email,
puid: el.val().puid,
username: el.val().username,
time: el.val().time,
text: el.val().text,
},
];
this.setState({
data2: this.state.data2.concat(this.state.data),
});
}
});
this.state.data2.forEach(obj => {
if (!this.state.newArray.some(o => o.puid === obj.puid)) {
this.state.newArray.push({...obj});
}
});
this.setState({
posts: this.state.newArray,
postsCount: _.size(this.state.newArray),
});
console.log('valeur finale POSTS=' + this.state.posts);
});
}
renderPosts() {
const postArray = [];
_.forEach(this.state.posts, (value, index) => {
const time = value.time;
const timeString = moment(time).fromNow();
postArray.push(
<TouchableOpacity
onLongPress={this._handleDelete.bind(this, value.puid)}
key={index}>
<PostDesign
posterName={value.username}
postTime={timeString}
postContent={value.text}
/>
</TouchableOpacity>,
);
//console.log(postArray);
});
_.reverse(postArray);
return postArray;
}
_handleDelete(puid) {
const email = firebase.auth().currentUser.email;
let user_email = firebase.database().ref('/posts');
user_email.once('value').then(snapshot => {
snapshot.forEach(el => {
console.log('Userdb :' + el.val().email);
if (email === el.val().email) {
Alert.alert(
'Supprimer le message',
'Are you sure to delete the post?',
[
{text: 'Oui', onPress: () => this._deleteConfirmed(puid)},
{text: 'Non'},
],
);
//console.log('Userdb :' + el.val().email);
} else {
//
console.log('Usercur :' + email);
}
});
});
}
_deleteConfirmed(puid) {
const uid = firebase.auth().currentUser.uid;
firebase
.database()
.ref('/posts/' + uid + puid)
.remove();
this.setState({
posts: this.state.newArray.filter(user => user.puid !== puid),
});
}
render() {
return (
<View style={styles.container}>
<View style={styles.profileInfoContainer}>
<View style={styles.profileNameContainer}>
<Text style={styles.profileName}>{this.props.email}</Text>
</View>
<View style={styles.profileCountsContainer}>
<Text style={styles.profileCounts}>{this.state.postsCount}</Text>
<Text style={styles.countsName}>POSTS</Text>
</View>
</View>
<ScrollView styles={styles.postContainer}>
{this.renderPosts()}
</ScrollView>
</View>
);
}
}
Thank you in advance !!
Several places in your code you are accessing this.state inside of setState, which can cause problems like this. You should be using a function with prevProps whenever you are accessing state within setState.
For example, within _deleteConfirmed:
this.setState({
posts: this.state.newArray.filter(user => user.puid !== puid),
});
should be changed to:
this.setSate(prevState => ({
posts: prevState.newArray.filter(user => user.puid !== puid),
});

Redux updating object in an array (React-Native)

Trying to learn Redux. I am building a list app. From the home screen you can see all your lists and click on one to update. You can also create a new list.
So I've made a check to see if you navigate to the list component with data, the action upon 'save' will be UPDATE_LIST. If you navigate to the list component with no data, the action upon 'save' will be NEW_LIST. The new list works but the update does not. If you need to see more files, let me know. Thank you.
This is the list component:
import React from 'react';
import { StyleSheet, Text, View, Button, TextInput } from 'react-native';
import { connect } from 'react-redux';
import { newList, updateList } from '../store/tagActions';
class List extends React.Component {
constructor(props){
super(props);
this.state = {
title: '',
tags: [],
mentions: [],
tagValue: '',
mentionValue: '',
id: null
}
}
submitTag = (text) => {
this.setState({
tags: [
...this.state.tags,
text
],
tagValue: ''
})
}
submitMention = (text) => {
this.setState({
mentions: [
...this.state.mentions,
text
],
mentionValue: ''
})
}
componentDidMount() {
if (this.props.route.params.data !== null) {
const { title, tags, mentions, id } = this.props.route.params
this.setState({
id: id,
title: title,
tags: tags,
mentions: mentions
})
} else return
}
save = () => {
if (this.props.route.params.data !== null) {
this.props.updateList(
id = this.state.id,
title = this.state.title,
tags = this.state.tags,
mentions = this.state.mentions
)
} else {
this.props.newList(
title = this.state.title,
tags = this.state.tags,
mentions = this.state.mentions
)
}
this.props.navigation.navigate('Home');
}
render() {
return (
<View style={styles.container}>
<TextInput //==================================== TITLE
value={this.state.title}
style={styles.title}
placeholder='add Title..'
onChangeText={text => this.setState( {title: text} ) }
/>
<View style={styles.allTags}>
<Text>{this.state.id}</Text>
<View style={styles.tagsList}>
{
this.state.tags.map((tag => (
<Text key={tag} style={styles.tags}>#{tag}</Text>
)))
}
</View>
<View style={styles.mentionsList}>
{
this.state.mentions.map((mention => (
<Text key={mention} style={styles.mentions}>#{mention}</Text>
)))
}
</View>
</View>
<TextInput // =================================== TAGS
value={ this.state.tagValue }
style={styles.tagsInput}
placeholder='add #Tags..'
placeholderTextColor = "#efefef"
autoCorrect = { false }
autoCapitalize = 'none'
onChangeText={text => this.setState( {tagValue: text}) }
onSubmitEditing={() => this.submitTag(this.state.tagValue)}
/>
<TextInput //===================================== MENTIONS
value={ this.state.mentionValue }
style={styles.mentionsInput}
placeholder='add #Mentions..'
placeholderTextColor = "#efefef"
autoCorrect = { false }
autoCapitalize = 'none'
onChangeText={text => this.setState( {mentionValue: text})}
onSubmitEditing= {() => this.submitMention(this.state.mentionValue)}
/>
<Button
title='save'
onPress={() => {
this.save();
}
}
/>
</View>
)
}
}
const mapStateToProps = (state) => {
return { state }
};
export default connect(mapStateToProps, { newList, updateList }) (List);
tagActions.js
let nextId = 0;
export const newList = (title, tags, mentions) => (
{
type: 'NEW_LIST',
payload: {
id: ++nextId,
title: title,
tags: tags,
mentions: mentions
}
}
);
export const updateList = (title, tags, mentions, id) => (
{
type: 'UPDATE_LIST',
payload: {
id: id,
title: title,
tags: tags,
mentions: mentions
}
}
);
tagReducer.js:
const tagReducer = (state = [], action) => {
switch (action.type) {
case 'NEW_LIST':
//add tags and mentions later
const { id, title, tags, mentions } = action.payload;
return [
...state,
{
id: id,
title: title,
tags: tags,
mentions: mentions
}
]
case 'UPDATE_LIST':
return state.map((item, index) => {
if (item.id === action.payload.id) {
return {
...item,
title: action.payload.title,
tags: action.payload.tags,
mentions: action.payload.mentions
}
} else { return item }
})
default:
return state;
}
};
export default tagReducer;
By sending args like so
export const updateList = (title, tags, mentions, id) => (
In the scope of the function, the first arg that the function will be called with gonna be title, and even by doing something like this
this.props.updateList(
id = this.state.id,
title = this.state.title,
tags = this.state.tags,
mentions = this.state.mentions
)
what you sent as this.state.id, gonna be evaluate as title. (not python alert)
so you have two options, either organize args as in function, or send object with keys
this.props.updateList({
id: this.state.id,
title: this.state.title,
tags: this.state.tags,
mentions: this.state.mentions
})
export const updateList = ({title, tags, mentions, id}) => (
Anyhow, of course you can use array as data structure for state, sorry I mislead you
const tagReducer = (state = [], action) => {
switch (action.type) {
const { id, title, tags, mentions } = action.payload || {};
case 'NEW_LIST':
//add tags and mentions later
return [ ...state, { id, title, tags, mentions } ]
case 'UPDATE_LIST':
return state.map(item =>
item.id === id ? { ...item, title, tags, mentions} : item
)
default: return state;
}
};
export default tagReducer;

How to execute a function when some item renders in react native?

I have a sectionlist of Contacts where I am displaying both device and online contacts of a user. The online contacts api doesnt give me all the contacts at once. So I have to implement some pagination. I am also fetching all device contacts and first page of online contacts and sorting them to show in sectionlist, but the problem is, to load more contacts, i have to keep track of the last item rendered in my state and in the render function I am calling pagination function to load more contacts. and then i am updating the state of fetched online contact. But its an unsafe operation, is there a better way to achieve this?
I want to execute a function when the specific item renders and it can update the state.
Here is some code: ContactList.tsx
import React, { Component } from "react";
import {
View,
StyleSheet,
SectionListData,
SectionList,
Text
} from "react-native";
import { Contact } from "../../models/contact";
import ContactItem from "./contact-item";
export interface ContactsProps {
onlineContacts: Contact[];
deviceContacts: Contact[];
fetchMoreITPContacts: () => void;
}
export interface ContactsState {
loading: boolean;
error: Error | null;
data: SectionListData<Contact>[];
lastItem: Contact;
selectedItems: [];
selectableList: boolean;
}
class ContactList extends Component<ContactsProps, ContactsState> {
private sectionNames = [];
constructor(props: ContactsProps, state: ContactsState) {
super(props, state);
this.state = {
loading: false,
error: null,
data: [],
lastItem: this.props.onlineContacts[this.props.onlineContacts.length - 1]
};
for (var i = 65; i < 91; ++i) {
this.sectionNames.push({
title: String.fromCharCode(i),
data: []
});
}
}
private buildSectionData = contacts => {
this.sort(contacts);
const data = [];
const contactData = this.sectionNames;
contacts.map(contact => {
const index = contact.name.charAt(0).toUpperCase();
if (!data[index]) {
data[index] = [];
contactData.push({
title: index,
data: []
})
}
data[index].push(contact);
});
for (const index in data) {
const idx = contactData.findIndex(x => x.title === index);
contactData[idx].data.push(...data[index]);
}
this.setState({
loading: false,
error: null,
lastItem: contacts[contacts.length - 1],
data: [...contactData]
});
};
private sort(contacts) {
contacts.sort((a, b) => {
if (a.name > b.name) {
return 1;
}
if (b.name > a.name) {
return -1;
}
return 0;
});
}
componentDidMount() {
const contacts = [].concat(
this.props.deviceContacts,
this.props.onlineContacts
);
this.buildSectionData(contacts);
}
componentDidUpdate(
prevProps: Readonly<ContactsProps>,
prevState: Readonly<ContactsState>,
snapshot?: any
): void {
if (this.props.onlineContacts !== prevProps.onlineContacts) {
const from = this.props.itpContacts.slice(
prevProps.onlineContacts.length,
this.props.onlineContacts.length
);
this.buildSectionData(from);
}
}
renderItem(item: any) {
if (!!this.state.lastItem && !this.state.loading)
if (item.item.id === this.state.lastItem.id) {
this.setState({
loading: true
});
this.props.fetchMoreOnlineContacts();
}
return <ContactItem item={item.item} />;
}
render() {
return (
<View style={styles.container}>
<SectionList
sections={this.state.data}
keyExtractor={(item, index) => item.id}
renderItem={this.renderItem.bind(this)}
renderSectionHeader={({ section }) =>
section.data.length > 0 ? (
<Text style={styles.sectionTitle}>
{section.title}
</Text>
) : null
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
sectionTitle: {
paddingBottom: 30,
paddingLeft: 25,
fontWeight: "bold",
fontSize: 20
}
});
export default ContactList;
Yeah after some thoughts I got the answer may be.
instead of calling fetchMoreContacts from renderItem I passed the lastItem as a prop to the ContactItem component.
and in the constructor I checked If the item is lastItem and called to fetchMoreContact.
and it worked!

Action not updating reducer when invoked

I cannot get my reducer to update. I can step into the action when I fire this.completeQuiz(id) in my debugger but my state doesn't get updated. Any ideas?
import {
submitAnswer,
resetQuiz,
nextQuestion,
completeQuiz
} from "../actions/videos";
class TestYourselfScreen extends React.Component {
constructor(props) {
super(props);
this.onCapture = this.onCapture.bind(this);
}
completeQuiz = id => {
let video = this.props.videos.find(obj => obj.id == id);
let correctAnswers = video.results.correctAnswers;
const questionsFiltered = video.questions.filter(obj => obj.question != "");
completeQuiz({
id,
totalScore: correctAnswers.length / questionsFiltered.length
});
};
render() {
.....
return (
{questionsFiltered.length > 0 && !completed && (
<View
style={{
flex: 1
}}
>
....
<Button
title={lastQuestion ? "Finish" : "Next"}
buttonStyle={[styles.button]}
disabled={
!results.correctAnswers.includes(current) &&
!results.incorrectAnswers.includes(current)
? true
: false
}
onPress={() =>
lastQuestion ? this.completeQuiz(id) : this.next(id, current)
}
/>
</View>
)}
{completed === true && (
<View
style={{
flex: 1
}}
>
<ViewShot ref="viewShot" options={{ format: "jpg", quality: 0.9 }}>
...
</View>
)}
</ScrollView>
);
}
}
const mapStateToProps = state => {
return {
videos: state.tcApp.videos
};
};
const mapDispatchToProps = dispatch => ({
submitAnswer: data => dispatch(submitAnswer(data)),
resetQuiz: id => dispatch(resetQuiz(id)),
nextQuestion: data => dispatch(nextQuestion(data)),
completeQuiz: data => dispatch(completeQuiz(data))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(TestYourselfScreen);
Action:
export const completeQuiz = data => ({
type: "COMPLETE",
data
});
Reducer:
import { trimText } from "../helpers";
export function tcApp(
state = { videos: [], search: { videos: [], term: "" } },
action
) {
switch (action.type) {
....
case "COMPLETE": {
const { completed, totalScore, id } = action.data;
return {
videos: state.videos.map(video =>
video.id === id
? {
...video,
results: {
totalScore
},
completed: true
}
: video
),
search: { term: "", videos: [] }
};
}
default:
return state;
}
}
I think your action is available through props do it as this
completeQuiz = id => {
let video = this.props.videos.find(obj => obj.id == id);
let correctAnswers = video.results.correctAnswers;
const questionsFiltered = video.questions.filter(obj => obj.question != "");
this.props.completeQuiz({
id,
totalScore: correctAnswers.length / questionsFiltered.length
});
};
because we mapDispatchToProps
Hope it helps

Resources