javascriptreactjsfirebasegoogle-cloud-firestoresnapshot

Return value firestore onSnapshot in react


I have a onSnapshot query in a function:

//firebaseutil.js
export async function getShorts(uid) {
const q = query(collection(db, 'shorted'), where('owner', '==', uid));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const urls = [];
querySnapshot.forEach((doc) => {
  urls.push({
    url: doc.data().url,
    shorturl: doc.data().shorturl,
    hits: doc.data().hits,
  });
});
console.log(urls);
return urls;
});
}

Which correctly logs the data and relogs it if I change the data on the firestore's collection (as expected)

I am trying to access these data from a user dashboard this way:

//dashboard.js
import { getShorts } from '../lib/fbutils';
import { useEffect, useState } from 'react';

export default function Dashboard() {
  const [myurls, setUrls] = useState([]);

  useEffect(() => {
    const fetchShorts = async () => {
      if (user) {
        const urls = await getShorts(user.uid);
        setUrls(urls);
        console.log(myurls);
        console.log(urls);
      }
    };
    fetchShorts();
  }, []);

user.id is correctly set, whereas both urls and myurls are logged as undefined (I was thinking at least for a Promise pending)

what is wrong here? I usually use this pattern to retrieve data, but it's my first time I get data from a firestore subscription


Solution

  • The onSnapshot() does not return a promise and your getShorts() function returns before the data is received. You can return a promise from that function as shown below:

    let fetched = false; 
    
    export function getShorts(uid) {
      return new Promise((resolve, reject) => {
        const q = query(collection(db, 'shorted'), where('owner', '==', uid));
        const unsubscribe = onSnapshot(q, (querySnapshot) => {
          const urls = querySnapstho.docs.map((d) => ({ id: d.id, ...d.data() }))
          
          if (!fetched) {
            // URLs fetched for first time, return value
            fetched = true;
            resolve(urls);
          } else {
            // URLs fetched already, an update received.
            // TODO: Update in state directly
          }
        })
      }) 
    }
    

    This should return all the URLs when you call the function await getShorts(user.uid); but for the updates received later, you'll have to update them in the state directly because the promise has resolved now.