skip to Main Content

I have run into the firebase “IN” limit of 10. Although a workaround solution was already answered here:

Is there a workaround for the Firebase Query "IN" Limit to 10?

None of the solutions in that thread seem to work with the listener “onSnapshot”. For my use case (Vue 3), I have a composable/function call I that queries firebase passing in an array that could have up to 100 document ID values and returns an object as below.

Is this possible?

import { ref, watchEffect } from 'vue'
import { db } from '@/firebase/config'
import { collection, onSnapshot, query, where, documentId } from 'firebase/firestore'

const getUsersList = (idList) => { 
    // idList is an array of document ID's
    const documents = ref(null)
    let collectionRef = collection(db, 'users')
    collectionRef = query(collectionRef, where(documentId(), 'in', idList))
    // this fails if I pass in more than 10 elements in the array
    const unsub = onSnapshot(collectionRef, snapshot => {
        let results = []
        snapshot.docs.forEach(doc => {
            results.push({ ...doc.data(), id: doc.id })
        })
        // update values
        documents.value = results
    })
    watchEffect((onInvalidate) => {
        onInvalidate(() => unsub())
    })
    return { documents }
}

export default getCollectionRt

2

Answers


  1. Chosen as BEST ANSWER

    Since no replies here completely answered the question, I ended up paying a freelancer to take a look and here's what they came up with. The solution does seem to have a random issue I am trying to sort out when the underlying changes, one of the records will disappear. It does work, is in scope of the original question and seems to have solved the problem.

    import { ref, watchEffect } from 'vue'
    import { db } from '@/firebase/config'
    import { collection, onSnapshot, query, where, documentId } from 'firebase/firestore'
    
    
    const getUserList = (idList) => {
        console.log('idList', idList)
        let documents = ref(null)
        let collectionRef = collection(db, 'users')
    
        let unsub, unsubes = [], resultsList = [{}];
        for (let i = 0; i < idList.length; i += 10) {
            let idList1 = idList.slice(i, i + 10); //console.log(idList1);
            let collectionRef1 = query(collectionRef, where(documentId(), 'in', idList1))
            unsub = onSnapshot(collectionRef1, snapshot => {
                let results = []
                snapshot.docs.forEach(doc => {
                    results.push({ ...doc.data(), id: doc.id })
                })
                resultsList.splice(resultsList.length, 0, ...results);
                console.log('results', results)
                documents.value = results
            })
            unsubes.push(unsub);
        }
        watchEffect((onInvalidate) => {
            onInvalidate(() =>{ unsubes.forEach(unsub => { unsub();         console.log("unsut", unsub); }) });       
        })
        Promise.all(unsubes);
        resultsList.shift(0);
        console.log("docu", documents.value);
        return { documents };
    }
    
    export default getUserList
    

  2. You will have to initialize multiple listeners i.e. same number of queries but with onSnapshot() (might be better than setting up a listener for each individual document). Try:

    import { ref } from 'vue';
    import { collection, query, where, documentId, onSnapshot } from 'firebase/firestore'
    
    const users = ref({})
    const dataLoaded = ref(false)
    
    const addFirestoreListeners = async () => {
      const idList = [];
    
      for (let i = 0; i < idList.length; i += 10) {
        const items = idList.slice(i, i + 10)
    
        const q = query(collection(db, 'users'), where(documentId(), 'in', items))
    
        onSnapshot(q, (snapshot) => {
          if (dataLoaded.value) {
            snapshot.docChanges().forEach((change) => {
              if (change.type === 'added' || change.type === 'modified') {
                users.value[change.doc.id] = change.doc.data()
              } else (change.type === 'removed') {
                users.value[change.doc.id] = null
              }
            })
          } else {
            snapshot.forEach((doc) => {
              users.value[doc.id] = doc.data()
            })
    
            dataLoaded.value = true
          }
        })
      }
    }
    

    The dataLoaded flag checks if the listeners have fetched data for first time or has received an update. I’ve use a map where the key is document ID so it can be removed easily but do change that to an array or any other required structure.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search