I'm trying to handle the state for a 'heart' icon in a rendered Flat List (which loads data from Firebase) for each individual item within the Flat List.
The code works, in that the heart icon fills in and the data is pushed to the database when the icon is pressed. Likewise, pressing the heart icon again reverts the icon and removes the 'like' from the database.
However, when the heart icon is clicked, it swaps between the filled in state and hollow state for the heart icon for every item in the list, when I'm trying to alter state for that specific item.
I understand that I need to handle state locally for each item in the Flat List, but I've no idea how to do it. Any help would be appreciated. Code below:
import React, {Component} from 'react';
import {
FlatList,
Text,
View,
} from 'react-native';
import {Icon} from 'react-native-elements';
import {globalStyles} from '../config/Styles';
import Firebase from 'firebase';
import 'firebase/database';
export default class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {
//set value of postList variable as an empty array
postList: [],
liked: false,
};
}
componentDidMount() {
this.getPostData();
}
getPostData = () => {
const ref = Firebase.database().ref('/posts');
ref.on('value', snapshot => {
const postsObject = snapshot.val();
if (!postsObject) {
console.log('NO DATA IN FIREBASE:', Date(Date.now()));
} else {
console.log('HOMESCREEN FIREBASE DATA RETRIEVED:', Date(Date.now()));
const postsArray = Object.values(postsObject);
this.setState({postList: postsArray});
}
});
};
render() {
return (
<View>
<FlatList
keyExtractor={post => post.id}
data={this.state.postList}
renderItem={({item: post}) => (
<View style={globalStyles.postContainer}>
<Text style={globalStyles.postText}>
{post.heading}
{'\n'}@{' '}
<Text style={{fontWeight: 'bold'}}>{post.location}</Text>
{'\n'}
{post.description}
{'\n'}
listed by{' '}
<Text style={{fontWeight: 'bold'}}>{post.createdBy}</Text>
{'\n'}
on <Text style={{fontWeight: 'bold'}}>{post.createdAt}</Text>
</Text>
<View style={globalStyles.iconMargin}>
<Icon
raised
iconStyle={globalStyles.icon}
name={this.state.liked ? 'heart' : 'heart-o'}
size={28}
type="font-awesome"
onPress={() => {
const userKey = Firebase.auth().currentUser.uid;
const postKey = post.id;
const favRef = Firebase.database().ref(
'favourites/' + userKey + '/' + postKey,
);
if (this.state.liked === false) {
favRef.set({
id: postKey,
heading: post.heading,
description: post.description,
location: post.location,
createdAt: post.createdAt,
createdBy: post.createdBy,
});
this.setState({liked: true});
} else {
favRef.remove();
this.setState({liked: false});
}
}}
/>
<Icon
raised
iconStyle={globalStyles.icon}
name="flag-o"
size={28}
type="font-awesome"
onPress={() =>
this.props.navigation.navigate('ReportPostScreen', post)
}
/>
</View>
</View>
)}
/>
</View>
);
}
}
Ok so the issue is that you've got a singular liked
state value instead of an array. You should firstly change liked
to an array (which will store the id of the posts which are liked). Maybe call it something more appropriate such as likePosts
. Then you can add or remove post ids from the array when they're liked or unliked (and check the likedPosts
array for the value when deciding what icon to display).
Your modified code should look something like this:
import React, {Component} from 'react';
import {
FlatList,
Text,
View,
} from 'react-native';
import {Icon} from 'react-native-elements';
import {globalStyles} from '../config/Styles';
import Firebase from 'firebase';
import 'firebase/database';
export default class HomeScreen extends Component {
constructor(props) {
super(props);
this.state = {
//set value of postList variable as an empty array
postList: [],
likedPosts: [],
};
}
componentDidMount() {
this.getPostData();
}
getPostData = () => {
const ref = Firebase.database().ref('/posts');
ref.on('value', snapshot => {
const postsObject = snapshot.val();
if (!postsObject) {
console.log('NO DATA IN FIREBASE:', Date(Date.now()));
} else {
console.log('HOMESCREEN FIREBASE DATA RETRIEVED:', Date(Date.now()));
const postsArray = Object.values(postsObject);
this.setState({postList: postsArray});
}
});
};
render() {
return (
<View>
<FlatList
keyExtractor={post => post.id}
data={this.state.postList}
renderItem={({item: post}) => (
<View style={globalStyles.postContainer}>
<Text style={globalStyles.postText}>
{post.heading}
{'\n'}@{' '}
<Text style={{fontWeight: 'bold'}}>{post.location}</Text>
{'\n'}
{post.description}
{'\n'}
listed by{' '}
<Text style={{fontWeight: 'bold'}}>{post.createdBy}</Text>
{'\n'}
on <Text style={{fontWeight: 'bold'}}>{post.createdAt}</Text>
</Text>
<View style={globalStyles.iconMargin}>
<Icon
raised
iconStyle={globalStyles.icon}
name={this.state.likedPosts.indexOf(post.id) > -1 ? 'heart' : 'heart-o'}
size={28}
type="font-awesome"
onPress={() => {
const userKey = Firebase.auth().currentUser.uid;
const postKey = post.id;
const favRef = Firebase.database().ref(
'favourites/' + userKey + '/' + postKey,
);
// This checks that the array doesn't contain the post id (i.e. the post was not previously liked)
if (this.state.likedPosts.indexOf(post.id) === -1) {
favRef.set({
id: postKey,
heading: post.heading,
description: post.description,
location: post.location,
createdAt: post.createdAt,
createdBy: post.createdBy,
});
// Include the post.id in the likedPosts array
this.setState({ likedPosts: [...this.state.likedPosts, post.id] })
} else {
favRef.remove();
// Remove the post.id from the likedPosts array
let index = this.state.likedPosts.indexOf(post.id);
this.setState({ likedPosts: this.state.likedPosts.splice(index, 1) })
}
}}
/>
<Icon
raised
iconStyle={globalStyles.icon}
name="flag-o"
size={28}
type="font-awesome"
onPress={() =>
this.props.navigation.navigate('ReportPostScreen', post)
}
/>
</View>
</View>
)}
/>
</View>
);
}
}