Home > database >  Trying to query multiple documents from Firestore in React at once, using an array from the users pr
Trying to query multiple documents from Firestore in React at once, using an array from the users pr

Time:12-14

This is my current code:

useEffect(() => {
    profile.familyCode.forEach((code) => {
      console.log(code._id)
      onSnapshot(query(collection(db, "group-posts", code._id, "posts"), orderBy("timestamp", "desc")
      ),
      (querySnapshot) => {
        const posts = querySnapshot.docs.map((doc) => ({
          id: doc.id,
          ...doc.data(),
        }));
        setMessages([...messages, posts])        
      }
      )
    })

There are TWO code._id's and currently it is only setting my messages from one of them. What am I missing here?

Ive tried using some of firestores logical expressions to do the same thing with no success. This way I can at least pull some of them, but I would like to pull ALL of the posts from BOTH code._id's

CodePudding user response:

You are missing the fact that setMessages is not updating messages itself immediately. So messages are closure-captured here with the old (or initial value) and calling setMessages will just overwrite what was previously set by previous onSnapshot.

Next issue - onSnapshot returns the unsubscribe function which should be called to stop the listener. Or you will get some bugs and memory leaks.

Here is a fast-written (and not really tested) example of possible solution, custom hook.

export function useProfileFamilyGroupPosts(profile) {
  const [codeIds, setCodeIds] = useState([]);
  const [messagesMap, setMessagesMap] = useState(new Map());

  const messages = useMemo(() => {
    if (!messagesMap || messagesMap.size === 0) return [];
    // Note: might need some tweaks/fixes. Apply .flatMap if needed.
    return Array.from(messagesMap).map(([k, v]) => v);
  }, [messagesMap])

  // extract codeIds only, some kind of optimization
  useEffect(() => {
    if (!profile?.familyCode) {
      setCodeIds([]);
      return;
    }
    const codes = profile.familyCode.map(x => x._id);
    setCodeIds(curr => {
      // primitive arrays comparison, replace if needed.
      // if curr is same as codes array - return curr to prevent any future dependent useEffects executions
      return curr.sort().toString() === codes.sort().toString() ? curr : codes;
    })
  }, [profile])

  useEffect(() => {
    if (!codeIds || codeIds.length === 0) {
      setMessagesMap(new Map());
      return;
    }
    const queries = codeIds.map(x => query(collection(db, "group-posts", x, "posts"), orderBy("timestamp", "desc")));
    const unsubscribeFns = queries.map(x => {
      return onSnapshot(x, (querySnapshot) => {
        const posts = querySnapshot.docs.map((doc) => ({
          id: doc.id,
          ...doc.data(),
        }));
        // update and re-set the Map object.
        setMessagesMap(curr => {
          curr.set(x, posts);
          return new Map(curr)
        })
      });
    });

    // we need to unsubscribe to prevent memory leaks, etc
    return () => {
      unsubscribeFns.forEach(x => x());
      // not sure if really needed
      setMessagesMap(new Map());
    }
  }, [codeIds]);

  return messages;
}

The idea is to have a Map (or just {} key-value object) to store data from snapshot listeners and then to flat that key-value to the resulting messages array. And to return those messages from hook.

Usage will be

const messages = useProfileFamilyGroupPosts(profile);
  • Related