8

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 :)

Tramedol
  • 83
  • 3
  • Have you check this Firebase documentation on [paginating a query](https://firebase.google.com/docs/firestore/query-data/query-cursors#paginate_a_query)? – RJC Jan 25 '22 at 09:12
  • Thank you for the comment! I have looked through this section of the docs and that helped me get a working solution for sorting by ascending. Doesn’t look like the docs have a solution for orderBy descending AND pagination. – Tramedol Jan 26 '22 at 02:23
  • 1
    Does this answer your question? [Firestore how to combine orderBy desc with startAfter(null)](https://stackoverflow.com/questions/54847796/firestore-how-to-combine-orderby-desc-with-startafternull) – Jesús Fuentes Apr 03 '23 at 08:58

1 Answers1

2

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
RJC
  • 1,224
  • 2
  • 12
  • Might I ask how you would re-order it client-side? – Tramedol Jan 29 '22 at 13:25
  • 1
    I've updated my answer and provided a code that you can check. – RJC Jan 31 '22 at 09:56
  • This worked!! I now have 2 working options: [1]. Created a new field for each document that is -1*numViews. This is very hacky, and unsure if it will suit my scaling requirements down the line, but it works for my MVP! It also has the benefit of only calling 6 items in the array, as opposed to all db entries - so more cost effective. [2]. I followed your suggestion with a modification of splitting the function into 2 parts (getFirstReviews and getNextReviews). Your solution worked for first 6 reviews, and the next 6, but wouldnt get the third 6. Thank you so so much! – Tramedol Feb 07 '22 at 03:04
  • This answer is not correct. Ordering client-side prevents you retrieving the correct order of the documents from the database. This is the correct answer: https://stackoverflow.com/a/54848285/4173777 – Jesús Fuentes Apr 03 '23 at 08:56
  • @JesúsFuentes Answer in this thread and the answer in the thread that you have given, both are same. Also, there is no client-side ordering happening in the above code. Ordering is done using query, so it runs on the firebase server side. Correct me if I am wrong. Always ready to learn. :) – Anees Hameed May 15 '23 at 16:59
  • @AneesHameed Well, I don't know what I was thinking about. Maybe I wanted to write this in another Stackoverflow thread? Thanks for pointing out. Indeed the code is correct. – Jesús Fuentes May 16 '23 at 09:45
  • I appreciate your reply. Not many do that. :raised_hands: – Anees Hameed May 16 '23 at 12:15