Disc
Disc

Reputation: 61

Async/await specifically with Firestore forEach (React Native with Hooks)

Can someone provide some guidance on getting a specific item within a Firestore collection before the rest of the code moves on? I want to get the user item in the users collection before iterating through the reviews collection, and this is best achieved through using async/await as far as I know, but everywhere I look on stack overflow and elsewhere, they achieve it using setups/syntax much different to what I have been using.

Here's what I've got:

import React, { useState, useEffect } from "react";
import { Image, Text, TextInput, TouchableOpacity, View } from "react-native";
import styles from "./model/AccountStyles.js";
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
import { firebase } from "../firebase/config";

export default function AccountScreen({navigation }) {

  const [existingReviewsArray, setExistingReviewsArray] = useState([]);
  
  useEffect(() => {
    let userID; 

    //get userID
    firebase.auth().onAuthStateChanged((user) => {
        const usersRef = firebase.firestore().collection("users"); 
        usersRef 
          .doc(user.uid)
          .get()
          .then((document) => {
            const data = document.data();
            userID = data.id 
          });
    });

    //get reviews made by userID to useState
    var addToReviewsArray = [];
    const reviewsRef = firebase.firestore().collection('reviews');
    reviewsRef.get().then((querySnapshot) => {
      querySnapshot.forEach(snapshot => {
          if (snapshot.data().userID == userID){
            var existingReview = snapshot.data(); 
            addToReviewsArray = ([...addToReviewsArray , existingReview]);          
          } 
      }
    )    
    setExistingReviewsArray(addToReviewsArray);
    });
    }, []); 

What would be the best way to go about this? Would appreciate any guidance here.

Upvotes: 1

Views: 685

Answers (1)

Labu
Labu

Reputation: 2582

Explanation of the code below

useEffect hooks are async by design, and the Firebase API is too, so what I try to do clearly with this example is show how to implement a reactive programming approach. I don't use the async/await pattern here. This may also not be the best way of doing things - it's just the first thing that came to mind. :)

The code below "reacts" to the change of the user's auth state because initially the userId is undefined:

const [userId, setUserId] = useState(); // this means userId is undefined

The userId variable is populated in the first useEffect, which has no dependencies (the empty []), meaning that it should only run once when the component is mounted.

There is then a second useEffect hook created to listen to changes in the userId variable, which runs a function that can then use the newly populated variable.


Here's how I would do it. :)

import { useState, useEffect } from "react";

export default function AccountScreen({ navigation }) {
  const [userId, setUserId] = useState();
  const [existingReviewsArray, setExistingReviewsArray] = useState([]);

  // Create a useEffect [with no dependencies] that runs once to get the userId
  useEffect(() => {
    firebase.auth().onAuthStateChanged((user) => {
      const usersRef = firebase.firestore().collection("users");
      usersRef
        .doc(user.uid)
        .get()
        .then((document) => {
          const data = document.data();

          // Use the set state hook here to trigger the second useEffect below
          setUserId(data.id);
        });
    });
  }, []);

  // Create a useEffect that is triggered whenever userId is changed
  useEffect(() => {
    // get reviews made by userID to useState
    var addToReviewsArray = [];
    const reviewsRef = firebase.firestore().collection("reviews");

    reviewsRef.get().then((querySnapshot) => {
      querySnapshot.forEach((snapshot) => {
        if (snapshot.data().userID == userID) {
          var existingReview = snapshot.data();
          addToReviewsArray = [...addToReviewsArray, existingReview];
        }
      });

      setExistingReviewsArray(addToReviewsArray);
    });
  }, [userId]);

  // Use your `existingReviewsArray` in the render
}

Upvotes: 1

Related Questions