Tramedol
Tramedol

Reputation: 103

Firestore pagination: startAfter() returning no data when when using orderBy() descending, but working for ascending

CONTEXT

First question here so thank you all in advance and please do let me know if you require anything else to help answer my question!

I'm creating a sorted list of HTML cards. The cards pull data from Firestore documents that each have metadata: numViews and summary.

I am loading paginated data of 5 cards at a time, and want a 'load more' button to reveal the next 5.

Im following this tutorial: https://youtu.be/vYBc7Le5G6s?t=797 (timestamped to part on orderBy, limit, and creating a Load More button).

Not sure if its relevant but I eventually intend to make this an infinite scroll, ideally using the same tutorial.

PROBLEM

I have a working solution (exactly the same as tutorial) for sorting by ascending (see below)). It creates cards for the documents with the lowest number of views, increasing by views as you scroll down the page. The load more button creates the next 5 cards.

When I change to orderBy() descending, no cards load. When I change the pagination from startAfter() to endBefore(), and orderBy(descending) it sorts the first 5 by descending correctly (starting with the highest views, and descending as you scroll down the page), but the Load More button just reloads the same 5 cards, not the next 5.

Here is my code

// References an empty container where my cards go
const container = document.querySelector('.containerload');

// Store last document
let latestDoc = null;

const getNextReviews = async () => {
  // WORKING ascending
  var load = query(colRef, orderBy('numViews', 'asc'), startAfter(latestDoc || 0), limit(5))

  // BROKEN descending - returns nothing
  // var load = query(colRef, orderBy('numViews', 'desc'), startAfter( latestDoc || 0), limit(5))

  // HALF-BROKEN descending - returns 5 cards with highest views, but load more returns the same 5
  // var load = query(colRef, orderBy('numViews', 'desc'), endBefore(latestDoc || 0), limit(5))

  const data = await getDocs(load);

  // Output docs
  let template = '';
  data.docs.forEach(doc => {
    const grabData = doc.data();
    template += `
    <div class="card">
      <h2>${grabData.summary}</h2>
      <p>Views ${grabData.numViews}</p>
    </div>
    `
  });
  container.innerHTML += template; 

  // Update latestDoc
  latestDoc =  data.docs[data.docs.length-1]

  // Unattach event listeners if no more documents
  if (data.empty) {
    loadMore.removeEventListener('click',handleClick)
  }

}

// Load more docs (button)
const loadMore = document.querySelector('.load-more button');

const handleClick = () => {
  getNextReviews();
  console.log(latestDoc);
}

loadMore.addEventListener('click', handleClick);

// wait for DOM content to load
window.addEventListener('DOMContentLoaded', () => getNextReviews());

I have looked through a number of similar questions and cannot get a solution working:

Firestore startAfter() returning the same data in infinite scrolling when ordered by descending timestamp - Cannot refactor solution to my circumstances

How to combine Firestore orderBy desc with startAfter cursor - Cannot refactor solution to my circumstances (this uses Firebase v8)

Firestore how to combine orderBy desc with startAfter(null) - This suggested using endBefore which is how I ran into the problem of Load More loading the same 5 datapoints

I am unsure why orderBy('numViews', 'desc') and startAfter() returns nothing.

I think orderBy('numViews', 'desc') and endBefore() returns the same 5 because it is just getting 5 docs that end before latestDoc, which doesn't change to a cursor 5 documents down. I have tried playing around with this line but cannot get it to load the next 5 documents:

latestDoc =  data.docs[data.docs.length-1]

Thanks everyone :)

Upvotes: 10

Views: 3211

Answers (1)

RJC
RJC

Reputation: 1338

Upon re-checking the Firebase documentation on how to paginate a query, and following the given options from this related post:

  1. Use a Firebase query to get the correct data, then re-order it client-side
  2. Add a field that has a descending value to the data

It easier if you just add field like timestamp, for example:

Firestore collection: enter image description here

Code:

import { initializeApp } from "firebase/app";
import { getFirestore, collection, query, getDocs, orderBy, startAfter, limit } from "firebase/firestore";

const firebaseConfig = {
    projectId: "PROJECT-ID"
};

const app = initializeApp(firebaseConfig);
const db = getFirestore(app);
const q = query(collection(db, "users"));

const getNextReviews = async () => {
    // For this example, there's only 6 data
    // Query the first 5 data
    var load = query(q, 
        orderBy("timestamp", "desc"), 
        limit(5))
    
    // Retrieve the first 5 data
    const data = await getDocs(load);

    // Update latestDoc reference 
    const latestDoc =  data.docs[data.docs.length-1]

    // To output the retrieved first 5 data
    data.forEach((doc) => {
        // doc.data() is never undefined for query doc snapshots
        console.log("First: ", doc.data().first_name , " => ", doc.data().timestamp.toDate());
    });

    // Query the next data or remaining data 
    var next = query(q,
        orderBy("timestamp", "desc"),
        startAfter(latestDoc),
        limit(5));
        
    // Automatically pulls the remaining data in the collection
    const data_next = await getDocs(next);
        
    // Outputs the remaining data in the collection
    data_next.forEach((doc) => {
        // doc.data() is never undefined for query doc snapshots
        console.log("Second: ", doc.data().first_name , " => ", doc.data().timestamp.toDate());
    });
}

// You can add the other conditions for 'load-more button' in here
getNextReviews();

Result:

First:  bob  =>  2022-01-30T16:00:00.000Z
First:  mike  =>  2022-01-29T16:00:00.000Z
First:  teddy  =>  2022-01-26T16:00:00.000Z
First:  grace  =>  2022-01-25T16:00:00.000Z
First:  john  =>  2022-01-23T16:00:00.000Z
Second:  lory  =>  2022-01-19T16:00:00.000Z

Upvotes: 2

Related Questions