Search code examples
react-nativereact-native-flatlistsetstate

React Native: how to handle state for each item in a rendered FlatList when pressing the 'like' button?


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

Solution

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