Skip to content
Advertisement

Firestore data not displaying in my flatlist?

I am trying to display the posts of whoever a user is following:

  1. First, I query firestore and get the list of all the users the current user is following
  2. For each user in “following”, I get their posts
  3. I order their posts by data created, using a javascript sort function

Here is the code:

constructor() {
    super();
    this.firestoreRef = Firebase.firestore().collection('following').doc(Firebase.auth().currentUser.uid).collection('following');
    this.state = {
      isLoading: true,
      followingPosts: [],
    };
}

componentDidMount() {
    this.setState({isLoading: true})
    this.unsubscribe = this.firestoreRef.onSnapshot(this.getCollection);
}

componentWillUnmount(){
    this.unsubscribe();
}

getCollection = async (querySnapshot) => {
    const followingPosts = [];

    querySnapshot.forEach(async (res) => {
        await Firebase.firestore()
        .collection('globalPosts')
        .where("uid", "==", res.data().uid)
        .onSnapshot(function(query) {
            query.forEach((doc) =>  {
                const { 
                    ..Fields
                    } = doc.data();

                    followingPosts.push({
                        key: doc.id,
                        ..Fields
                    });
            })
            followingPosts.sort(function(a,b){ 
                return b.date_created.toDate() - a.date_created.toDate()
            })
        });
    })

    this.setState({
        followingPosts,
        isLoading: false, 
    })

}

The issue lies in setting state. If the “following posts” array is empty, I render this:

if (this.state.followingPosts.length == 0) {
    return(
        <View style={styles.emptyContainer}>
        <Text style = {styles.buttonText}>following feed coming soon!</Text>
        <TouchableOpacity  
            style = {styles.button} 
            onPress={() => this.onShare()} >
            <Text style = {styles.buttonText}>invite friends to traderank</Text>
        </TouchableOpacity>
    </View>
)}

And if not, I render the flatlist:

return (
    <View style={styles.view}>
        <FlatList
            data={this.state.followingPosts}
            renderItem={renderItem}
            keyExtractor={item => item.key}
            contentContainerStyle={{ paddingBottom: 50 }}
            showsHorizontalScrollIndicator={false}
            showsVerticalScrollIndicator={false}
            onRefresh={this._refresh}
            refreshing={this.state.isLoading}
        />
   </View>   
)

The array is always “empty” currently, because I am setting state OUTSIDE query snapshot.

But when I move my setState to inside the querySnapshot:

 querySnapshot.forEach(async (res) => {
        await Firebase.firestore()
        .collection('globalPosts')
        .where("uid", "==", res.data().uid)
        .onSnapshot(function(query) {
            query.forEach((doc) =>  {
                const { 
                    ...
                    } = doc.data();

        
                    followingPosts.push({
                        key: doc.id,
                        ...
                    });
            })

            followingPosts.sort(function(a,b) { 
                return b.date_created.toDate() - a.date_created.toDate()
            })
            console.log(followingPosts)

            this.setState({ <------------- here
                followingPosts,
                isLoading: false, 
            })
        
        }.bind(this))

    })
    console.log(followingPosts)

The posts show up fine, but the app crashes when a user the current user is following posts a post, because of how the post is written to firestore:

(from where a user creates a post):

 await Firebase.firestore()
    .collection('globalPosts')
    .add({
        uid: this.state.uid 
    })
    .then((docRef) => this.uploadToStorage(docRef.id))
    .catch(function(error) {
        console.error("Error storing and retrieving image url: ", error);
    });

    
    await Firebase.firestore()
    .collection('globalPosts')
    .doc(this.state.postID)
    .set ({
        ... More Fields
    })
    .catch(function(error) {
        console.error("Error writing document to global posts: ", error);
    });

Since querySnapshot in “Following” is always listening to firestore, the app crashes as soon as I write a post because of the first half of the creation code:

 await Firebase.firestore()
    .collection('globalPosts')
    .add({
        uid: this.state.uid <------------------------- only the UID is added initially
    })
    .then((docRef) => this.uploadToStorage(docRef.id))
    .catch(function(error) {
        console.error("Error storing and retrieving image url: ", error);
    });

The post is added to globalPosts, but the remaining fields are not added yet.

The only solutions I can think of are:

  1. Set state outside query snapshot, but when I tried that, the posts don’t show up since followingPosts.length is 0 and the state isn’t updated outside querySnapshot

  2. Figure out how to unmount the component, it seems like the component isn’t unmounting so the listener is always listening, which is what causes the crash

-A note about this. I have a globalPosts feed, where everything works 100%, and then when trying almost the identical implementation in my “following” feed, the app crashes when a user posts a new post

  1. Change how I create a post, which I would rather not do

EDIT: I am now going down the rabbit hole of why the component is mounting & therefore listening when the component should be unmounted. This could be the reason behind my crash!

Advertisement

Answer

your code is a bit off as you creating one listner per every user the current user is following and listners on every change in their posts. This will effect performance and double-triple your costs for Firestore. you should only have one listner for posts that the current user have interest in.

The idea is when the component mounts you get all the users the current user is following with normal get method and not a listner then add this users to the state. at the same time you should have a listner for globalPosts which get the posts by filtering upon a field of the post owner.

something like this:

// get users that current user is following
firestore
    .collection("users")
    .doc(auth.currentUser.uid)
    .get().then((doc) => this.setState({following: [...doc.data().following]}))



// set a listner for those users' posts and take advantage of **in** operator
    this.unsubscribeFirestore = firestore
    .collection("globalPosts")
    .where("owner", "in", following)
    .onSnapshot((snapshot) => {
      snapshot.docChanges().forEach((change) => {
        
        if (change.type === "added") {
          this.setState({
            followingPosts: [
              ...this.state.followingPosts,
              change.doc.data(),
            ],
          });
        }
      });
    });
Advertisement