Reem Obeid
Reem Obeid

Reputation: 85

How to join data from two collections by reference field in Firestore?

I have 2 collections in my firestore. One called owners and the second is unicorns. An owner has only a name field and a unicorn has a name and a reference to the owner. I want a query to return an array of objects that looks like this

unicorns = { id: 123,
                name: Dreamy,
                owner: { id: 1
                         name: John Cane
                       }
               }

my query looks like this but there is something missing that I can't figure out

let unis = [];
      db
      .collection("unicorns")
      .get()
      .then((querySnapshot) => {
        querySnapshot.forEach((doc) => {
          let uni = {
            id: doc.id,
            name: doc.data().name,
            owner: doc.data().owner,
          };
          if (uni.owner) {
            doc
              .data()
              .owner.get()
              .then((res) => {
                uni.owner = {
                  id: res.id,
                  name: res.data().name,
                };
                unis.push(uni);
                
              })
              .then(() => {
                setUnicorns(unis);
              })
              .catch((err) => console.error(err));
          } else {
            unis.push(uni);
          }
        });
      })

When I setUnicorns hook and I try to map the results I don't get all the data I need

Upvotes: 2

Views: 3082

Answers (1)

Dharmaraj
Dharmaraj

Reputation: 50900

You cannot run the .get() method on a string. You would have to run a separate request to firestore to get owner documents. I would recommend using a for-of inside a async function as shown below.

const db = admin.firestore()

//Declare the reference for collections before the loop starts
const ownersCollection = db.collection("owners")
const unicornsCollection = db.collection("unicorns")

const ownersData = {}

let unis = [];
unicornsCollection.get().then(async (querySnapshot) => {
    for (const doc of querySnapshot) {
        let uni = {
            id: doc.id,
            name: doc.data().name,
            //Assuming the owner field is the UID of owner
            owner: doc.data().owner,
        };
        if (uni.owner) {
            //Check if owner's data is already fetched to save extra requests to Firestore
            if (ownersData[uni.owner]) {
                uni.owner = {
                    id: ownersData[uni.owner].id,
                    name: ownersData[uni.owner].name,
                };
                unis.push(uni);
            } else {
                //User the ownersCollection Reference to get owner information
                ownersCollection.doc(uni.owner).get().then((ownerDoc) => {
                    uni.owner = {
                        id: ownerDoc.data().id,
                        name: ownerDoc.data().name,
                    };
                    ownersData[uni.owner] = {
                        id: ownerDoc.data().id,
                        name: ownerDoc.data().name,
                    }
                    unis.push(uni);
                }).then(() => {
                    setUnicorns(unis);
                }).catch((err) => console.error(err));
            }
        } else {
            unis.push(uni);
        }
    }
})

How this works? It fetches all the unicorn documents and iterates over then to check if the document has owner's UID. If yes then runs another request to Firestore to get that unicorn's owner's document. Let me know if you have any questions.

Edit: In case a single owner has multiple unicorns then you surely don't want to fetch the data of same owner again and again. So add that in an object locally and check if the data of that owner is already fetched before making a request to Firestore. Code for the same updated above.

Upvotes: 1

Related Questions