Firestore: Simple query to fill item list

I am attempted to load a componentUrl and have it issue a Firestore query every time its displayed to fill in a list of posts. The query in Firestore is asynchronous and I have not been able to find a way to load the Firestore data when I jump to the view. I am trying to stay as barebones F7 and Firestore as possible so please dont point me to VueJS or other frameworks.

Here is my latest attempt:

In app.js:

var offersView = app.views.create('#view-offers', {
  url: '/offers/'
});
function loadOffers() {
  const offers = [];
    
  firestore.collection("offers").orderBy('createdOn', 'desc').limit(25)
    .get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            offers.push(doc.data());
        });
    })
    .catch(function(error) {
        console.log("Error getting all offers: ", error);
    });
        
    return(offers);
}

In offers.html:

 return {
    data: function () {
      return {
        offers: []
      }
    },

In routes.js:

{
path: ‘/offers/’,
async(routeTo, routeFrom, resolve, reject) {
let offerdata = loadOffers();
resolve(
{
componentUrl: ‘./pages/offers.html’
},
// pass context
{
context: {
offers: offerdata,
},
}
);
}
},

I know that offers is returning with data from Firestore correctly and creating the array of dictionaries. There must be something with the timing of the async call to Firestore vs the page creation in F7. Is it the case that async in the route only handles F7’s wrappers for ajax and not random async calls?

Well, looks like your loadOffers function is async (e.g. it uses promise) and you try to use it like sync function, so you need to resolve your route when offers loaded, e.g.:

function loadOffers() {
  const offers = [];
    
  return firestore.collection("offers").orderBy('createdOn', 'desc').limit(25)
    .get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            offers.push(doc.data());
        });
        return offsers;
    })
    .catch(function(error) {
        console.log("Error getting all offers: ", error);
    });
}
{
  path: '/offers/',
    async(routeTo, routeFrom, resolve, reject) {
      loadOffsers().then((offers) => {
        resolve(
          {
            componentUrl: './pages/offers.html'
          },
          // pass context
          {
            context: {
              offers,
            },
          }
        );
      });
   }
},

Thank you. I was able to get it to work but only by using async/await and unpacking the firestore query a bit.

I changed the route as you suggested. The promise version of my query did not work because the loadOffers function returns before the promise is complete. Instead I had to unpack loadOffers to use async/await

My new loadOffers became:

async function loadOffers() {
const offers = [];

let offersRef = firestore.collection(‘offers’).orderBy(‘createdOn’, ‘desc’).limit(25);
let allOffersSnapShot = await offersRef.get();
allOffersSnapShot.forEach(doc => {
offers.push(doc.data());
});
return offers;

Then I was left with another question which was what to do with the <script> at the end of my offers.html which is a componentUrl. I ended up just removing the <script> function which set the data originally and this seemed to work. My page now loads with live data from Firestore!

Should I have removed this completely?

> <script>
>   return {
>     data: function () {
>       return {
>         offers: []
>       }
>     },
>   };
> </script>

Yes you can remove that script

I notice that the route async function is only called once when the view is created. Since my lists will be Most Recent and Most Liked lists I will want to update the views every so often. For instance, I might update the Most Recent list once every 30 minutes when the user tabs to that view + I might update the Most Liked list every hour.

If I want to include logic for when to update the list and to make a call to re-populate this list how would I do that?

Should I be using events in the ComponentUrl like PageBeforeIn? Will that work with the async initial code above?

Then you need to move that data fetching logic to page component and do all neccessary refreshes and updates within that page component

I attempted to do the following in the componentUrl file but is there something else I need to do to insure the routine returns the correct data to the page template?

return {
data: function () {
loadOffers().then((offersresult) => {
return {
offers: offersresult
}
});
}
};

Attempted the following in the route list, no luck. Guess I am not reading the docs correctly to understand where you put async fetching calls for database data so that a page gets updated every time its visible to the user (or on a logic check to see if it should be updated). All Firestore calls are async by design so integrating Firebase/Firestore requires getting this pattern correct. Not only are Firestore calls async but you also have the ability to get updates as the results of a query are changed (real time data updates) so that adds and extra challenge to the puzzle.

beforeEnter: function (routeTo, routeFrom, resolve, reject) {
    loadOffers().then((offers) => {
        console.log("offersin: ", offers);

        resolve(
          {
            componentUrl: './pages/offers.html'
          },
          // pass context
          {
            context: {
              offers,
            },
          }
        );
    });

I think it is a very common web app requirement to be able to load a feed, set of messages, photo stream, etc. and have that page update every so often. Its common enough that it would be good to have very explicit directions in the Framework7 documentation about how to load a dynamic list of messages and update them frequently (when user flips back to page -or- on a timer -or- when the database sends an update event). Currently the documentation is very obtuse on how to use routes with database requests. Its also not clear on when pages update and when they don’t.

If anyone reads the above thread and can unpack exactly how to update the data in my ComponentUrl (regularly) off an asynchronous database call (Firestore) I would be very greatful. I also intend to write a blog post for other Firebase users considering F7 with tips for getting started.

For now I am goin back to the original solution of using async in the route definition with hopes of figuring up refreshes later.

You need to put that logic in page component and do requests there when you need it:

<template>
  <div class="page">
    ...
    ul
  </div>
</template>
<script>
  return {
    data() {
      return {
        dbItems: [],
      };
    },
    mounted() {
      const self = this;
      self.getItems();
    },
    methods: {
      getItems() {
        const self = this;
        requestToDb()
          .then((items) => {
            self.$setState({ dbItems: items })
          })
      }
    }
  }
</script>

That examples does request when page component mounted (added to DOM), if you need it somewhere else then add it. E.g. if you need it also when user got back to that page, you can change it from mounted to on.pageAfterIn. If you need to it with interval then just wrap it with setInterval, etc.

Thank you. It looks like this works for creating the page and I will try on.pageAfterIn for updating it when viewed.

return {
data() {
return {
offers: [],
};
},
mounted() {
const self = this;
self.getItems();
},
methods: {
async getItems() {
const self = this;
let offerList = [];
let offersRef = firestore.collection(‘offers’).orderBy(‘createdOn’, ‘desc’).limit(25);
let allOffersSnapShot = await offersRef.get();
allOffersSnapShot.forEach(doc => {
offerList.push(doc.data());
});
self.$setState({ offers: offerList })
}
}
}