Reputation: 103
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
Reputation: 1338
Upon re-checking the Firebase documentation on how to paginate a query, and following the given options from this related post:
It easier if you just add field like timestamp
, for example:
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