user5410226
user5410226

Reputation:

Firestore startAfter() returning the same data in infinite scrolling when ordered by descending timestamp

I'm writing a profile page with chronological user posts (latest post on top) using Firestore on Ionic by setting orderBy() to "timestamp" descending. I'm using Ionic's infinite loading to load more posts when the user reaches the bottom, but the result is that Firestore loads the exact same posts over and over again. Please help!

Hi!

Sorry if this is a beginner question, but I've been wrapping my head around this for sever hours to no avail. The pagination works properly when in ascending order, but loads the same posts when in descending order. I've looked at trying the following alternatives, but they would not be cost-efficient:

  1. Change the limit when user reaches the bottom: this would lead to reading all the posts again and again
  2. Do it in ascending order but reverse the array: would defeat the purpose of pagination
  3. use a query (where) to grab the documents before x timestamp: works in theory, but is a bit hacky and I would really want to know how startAfter() works since it's already there.

.

'''

READ_posts__profile(uid,limit, start) : Promise<[]>
  {
      return new Promise((resolve, reject) =>
      {

         if (start == null)
         {
            resolve();
         }
        else if(start == 'head')
        {
            start = new Date();
        }
            console.log(start);


         this._DB.collection(this.ref.posts + uid + '/userposts' )
         .orderBy("timestamp", 'desc').startAfter(start).limit(limit)
         .get()
         .then((querySnapshot) =>
         {

            let obj : any = [];

            console.log(obj);

            querySnapshot
            .forEach((doc : any) =>
            {
               obj.push({
                  id             : doc.id,
                  content        : doc.data().content,
                  likes          : doc.data().likes,
                  timestamp      : doc.data().timestamp,
                  comments       : doc.data().comments
               });

            });

            resolve(obj);
         })
         .catch((error : any) =>
         {
            reject(error);
         });
      });
  }

'''

Expected result: Infinite scrolling of posts from latest to oldest Result: Infinite scrolling of first x posts again and again (limit is x)

Thank you for taking the time to read. Hope to hear from you guys soon!

UPDATE FOR FUTURE REFERENCE: Rather than using the doc itself in .startAfter(), it worked with using doc.timestamp like so:

      this._DB.collection(this.ref.posts + uid + '/userposts' )
  .orderBy("timestamp", "desc").startAfter(start.timestamp).limit(this.limit)
  .get()
  .then(querySnapshot =>
  {

Upvotes: 10

Views: 18131

Answers (5)

Agung
Agung

Reputation: 13803

I understand the OP uses Javascript SDK, I think I find a workaround for this issue. unfortunately currently I am using Flutter, but I think you can tweak it easily.

so the idea is to convert the Date or DateTime object from the language (Dart, Javascript, Kotlin etc) to Timestamp object from the Firestore library. and then pass that Timestamp object to startAfter

in Flutter, you can do it like this

import 'package:cloud_firestore/cloud_firestore.dart'; // to import Timestamp class from Firestore


var lastCreatedAtDateTime = yourObject.createdAt; // this is Date Time Object from your language
var firestoreTimestamp = Timestamp.fromDate(lastCreatedAtDateTime); // convert Date to be Firestore's timestamp

query = query.startAfter([firestoreTimestamp)]);

Upvotes: 0

paugil
paugil

Reputation: 3

The query should work if you make sure that the data type are the same between timestamp in firestore and variable start

Upvotes: 0

Juan Herrera
Juan Herrera

Reputation: 93

The problem is, that you have to match your last query order: The order of the field values must ""match the order of the order by clauses of the query""

so if you first make a query like this

 collectionReference
        .whereEqualTo("id", someId)
        .orderBy("timestamp", Query.Direction.DESCENDING)
        .limit(5)
        .get()
        .addOnSuccessListener { documentSnapshots ->
            //do some stuff
        }

Then your next query should be like:

collectionReference
        .whereEqualTo("id", someId) 
        .orderBy("timestamp", Query.Direction.DESCENDING) //FIELD
        .limit(5)
        .startAfter(lastVisible.get("timestamp"))
        .get()
        .addOnSuccessListener { documentSnapshots ->
            //do some stuff
        }

So you see that startAfter({somecriteria}) has to match the orderBy({somecriteria}) of your first query where you got your last doc visible from, otherwise it will return your first query again.

Upvotes: 4

Roy Ganor
Roy Ganor

Reputation: 111

In my case I added the query like this

query.startAfter(lastId)

where it should have been

query = query.startAfter(lastId)

Upvotes: 8

rtpHarry
rtpHarry

Reputation: 13125

The problem is that startAfter() is expecting a query cursor for the parameter, not the timestamp that you are passing.

A query cursor identifies the document:

What you need to do is save the doc to a variable at the class level and then pass that in with the next one:

// in the class variables at the top
latestEntry: any;

// then save a reference to it
this.latestEntry = data[data.length - 1].doc;

// Now you can use the latestEntry to query with startAfter
.startAfter(this.latestEntry)

This is the general theory. When I got this working myself in an app I used the code in this answer.

It's going to need quite a lot of rewriting of your code to do this, which is beyond the scope of the time I have right now, but hopefully this will help you resolve it yourself.

Upvotes: 4

Related Questions