React state is not updated immediately after deleting data - reactjs

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),
});

Related

Redux store is being updated but the component is not re-rendering

I have following reducer:
const INITIAL_STATE = {
wallets: [],
selectedAccount: null,
selectedNetwork:null
};
export const setActive = (id, payload) => ({
type: actionTypes.SET_ACTIVE,
id,
payload,
});
const editItem = wallets.map(item => (item.id !== action.id ? {
...item, active: false } : { ...item, active: true }));
case actionTypes.SET_ACTIVE:
return {
...state,
accounts: [...state.wallets,editItem],
selectedAccount: action.payload,
};
I console.logged everything and store is being updated but the problem is my component is not re-rendering for some reason.
setActiveFunc = item => {
const { actions } = this.props;
const selectedAcc = {
name: item.name,
address: item.address,
active: item.active,
id: item.id,
};
actions.setActive(item.id, selectedAcc);
};
const UserMenuAccount = ({ active, account, balance }) => (
<View style={styles.accountContainer}>
<Image
source={require('../../../assets/Usermenu/check.png')}
style={[styles.icon, { opacity: active ? 1 : 0 }]}
/>
<Text style={styles.text}>{account}</Text>
<Text style={[styles.text, { opacity: 0.5 }]}>
{balance}
{' '}
ETH
</Text>
</View>
);
class UserMenuAccounts extends Component {
setActiveFunc = item => {
const { actions } = this.props;
const selectedAcc = {
name: item.name,
address: item.address,
active: item.active,
id: item.id,
};
actions.setActive(item.id, selectedAcc);
};
render() {
const { GetWallets } = this.props;
return (
<View>
{GetWallets.map(users => (
<TouchableOpacity
onPress={() => {
this.setActiveFunc(users);
}}
key={users.id}
>
<UserMenuAccount
account={`${users.name}`}
balance={0}
active={users.active}
key={users.id}
/>
</TouchableOpacity>
))}
</View>
);
}
}
The function shall create checked icon once active=true and even tho reducer is doing its thing the icon is not appearing
This is the function I'm using in the component, any suggestions?
You forgot to add the 'accounts' in the initial state of your reducer:
const INITIAL_STATE = {
wallets: [],
accounts: [],
selectedAccount: null,
selectedNetwork:null
};
accounts: [...state.wallets,editItem],
should rather be
accounts: state.wallets.map(item => (item.id !== action.id ? { ...item, active: false } : { ...item, active: true }));
see map will create a new array above you were spreading an array and inserting another inside it

React native updating every 6th state automatically

Whenever I press the heart button it updates the state and calls renderHeart method in TutorsCard.js but it also updates every 6th card's state and so on when I scroll down to fetch more profiles so they appear to be liked as well. It might be an issue of shouldComponentUpdate in TutorsCard.js I change shouldComponentUpdate multiple times but it didn't work.
Feed.js
class Feed extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
profiles: new DataProvider((r1, r2) => {
return r1 !== r2;
}),
loading: false,
liked: false,
color: 'red',
refreshing: false,
limit: 10
};
this.layoutProvider = new LayoutProvider((i) => 0, (type, dim) => {
dim.width = width;
dim.height = 250;
})
}
onRefresh = () => {
this.getProfiles();
}
shouldComponentUpdate(nextProps, nextState) {
if (JSON.stringify(this.props) === JSON.stringify(nextProps) &&
this.state === nextState) {
console.log("should cmp update feed false")
return false
}
if (JSON.stringify(this.props) !== JSON.stringify(nextProps) ||
this.state !== nextState) {
console.log("should cmp update feed true")
return true
}
}
onEndReach = async () => {
this.setState({ limit: this.state.limit + 10 }, this.getProfiles)
}
rowRenderer = (type, data) => {
return (
<TutorCard
item={data}
navigation={this.props.navigation}
liked={
this.props.auth.likedProfiles.some((user) => user.id === data._id)
? true
: false
}
/>
);
}
TutorCard.js
class TutorCard extends Component {
state = {
liked: false,
};
shouldComponentUpdate(nextProps, nextState){
if(JSON.stringify(this.props.item._id) === JSON.stringify(nextProps.item._id) &&
this.state.liked === nextState.liked){
console.log("should cmp update tutors card false")
return false
}
if(JSON.stringify(this.props.item._id) !== JSON.stringify(nextProps.item._id) ||
this.state.liked !== nextState.liked){
console.log("should cmp update tutors card true")
return true
}
}
async likedCard() {
//server request
this.setState({ liked: false });
const res = await axios.post(IP_ADDRESS + '/api/removeLikedProfile',
{ id: this.props.item._id });
this.props.fetchCurrentUser()
}
async dislikeCard() {
//server request
this.setState({ liked: true });
const res = await axios.post(IP_ADDRESS + '/api/addLikedProfile',
{ id: this.props.item._id });
this.props.fetchCurrentUser()
}
capitalize = str => {
return str.charAt(0).toUpperCase() + str.slice(1);
};
renderHeart = () => {
if (!this.props.liked && !this.state.liked) {
return (
<TouchableOpacity onPress={() => this.dislikeCard()}>
<AntDesign name="hearto" size={normalize(23)} color="#26a03a" />
</TouchableOpacity>
);
}
if(this.props.liked || this.state.liked){
return (
<TouchableOpacity onPress={() => this.likedCard()}>
<AntDesign name="heart" size={normalize(23)} color="red" />
</TouchableOpacity>
);
}
}
render() {
return (
<View>
<Button style={{
flexDirection: 'row', flex: 1,
justifyContent: 'flex-end'
}}
transparent>{this.renderHeart()}</Button>
</View>
);
}
}

get element in react native

I want to create a Text element inside a View element, how do I link that? I've tried the following. (After typing inside the input, a search is made in the database and the result is translated into a text element).
class SearchScreen extends React.Component {
state = {
inputValue: "",
};
search() {
//Here I do the search in Firebase Realtime Database (it works)
var textElement = React.createElement(
Text,
{ style: { fontSize: 20 } },
[...] //Here inside is the retrieved data from the database
);
var resultView = useRef(resultView); //This doesn't work
ReactDOM.render(textElement, resultView);
}
setSearch = (inputValue) => {
this.setState({ inputValue }, () => this.search());
};
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => this.setSearch(inputValue)}
value={this.state.inputValue}
/>
<View ref="resultView">
</View>
</View>
)
}
}
export default SearchScreen;
ReactDom doesn't work with React Native.
Try something like this:
class SearchScreen extends React.Component {
state = {
inputValue: '',
resultText: '',
resultList: [],
};
search() {
//Here I do the search in Firebase Realtime Database (it works)
//[...] //Here inside is the retrieved data from the database
// Simulate request to the database
setTimeout(() => {
const databaseResultText = 'Hello World';
this.setState({
resultText: databaseResultText,
});
const databaseResultList = [
{
name: 'Bob',
},
{
name: 'Steve',
},
];
this.setState({
resultList: databaseResultList,
});
}, 1000);
}
setSearch = (inputValue) => {
this.setState({inputValue}, () => this.search());
};
render() {
return (
<View>
<TextInput
onChangeText={(inputValue) => this.setSearch(inputValue)}
value={this.state.inputValue}
/>
<View>
<Text>{this.state.resultText}</Text>
</View>
<View>
{this.state.resultList.map((item) => {
return <Text>{item.name}</Text>;
})}
</View>
</View>
);
}
}
export default SearchScreen;

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

React native delete multiple items from state array

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)
}));

Resources