Is there a way to implement back pagination with firestore? I am struggling to implement pagination with firestore, and there are limited firestore queries for it. Forward p
Simpler answer: Firestore now has .limitToLast(), which works exactly as you think it does. Used in my own (guess I need to publish it soon) Firestore Wrapper:
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
// *** Paginate API ***
export const PAGINATE_INIT = 0;
export const PAGINATE_PENDING = -1;
export const PAGINATE_UPDATED = 1;
export const PAGINATE_DEFAULT = 10;
export const PAGINATE_CHOICES = [10, 25, 50, 100, 250, 500];
/**
* @classdesc
* An object to allow for paginating a table read from Firestore. REQUIRES a sorting choice
* @property {Query} Query that forms basis for the table read
* @property {number} limit page size
* @property {QuerySnapshot} snapshot last successful snapshot/page fetched
* @property {enum} status status of pagination object
* @method PageForward pages the fetch forward
* @method PageBack pages the fetch backward
*/
export class PaginateFetch {
Query = null;
limit = PAGINATE_DEFAULT;
snapshot = null;
status = null; // -1 pending; 0 uninitialize; 1 updated;
/**
* ----------------------------------------------------------------------
* @constructs PaginateFetch constructs an object to paginate through large
* Firestore Tables
* @param {string} table a properly formatted string representing the requested collection
* - always an ODD number of elements
* @param {array} filterArray an (optional) 3xn array of filter(i.e. "where") conditions
* @param {array} sortArray a 2xn array of sort (i.e. "orderBy") conditions
* @param {ref} ref (optional) allows "table" parameter to reference a sub-collection
* of an existing document reference (I use a LOT of structered collections)
*
* The array is assumed to be sorted in the correct order -
* i.e. filterArray[0] is added first; filterArray[length-1] last
* returns data as an array of objects (not dissimilar to Redux State objects)
* with both the documentID and documentReference added as fields.
* @param {number} limit (optional)
* @returns {PaginateFetchObject}
**********************************************************************/
constructor(
table,
filterArray = null,
sortArray = null,
ref = null,
limit = PAGINATE_DEFAULT
) {
const db = ref ? ref : fdb;
this.limit = limit;
this.Query = sortQuery(
filterQuery(db.collection(table), filterArray),
sortArray
);
this.status = PAGINATE_INIT;
}
/**
* @method Page
* @returns Promise of a QuerySnapshot
*/
PageForward = () => {
const runQuery = this.snapshot
? this.Query.startAfter(_.last(this.snapshot.docs))
: this.Query;
this.status = PAGINATE_PENDING;
return runQuery
.limit(this.limit)
.get()
.then((QuerySnapshot) => {
this.status = PAGINATE_UPDATED;
//*IF* documents (i.e. haven't gone beyond start)
if (!QuerySnapshot.empty) {
//then update document set, and execute callback
//return Promise.resolve(QuerySnapshot);
this.snapshot = QuerySnapshot;
}
return this.snapshot.docs.map((doc) => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
});
});
};
PageBack = () => {
const runQuery = this.snapshot
? this.Query.endBefore(this.snapshot.docs[0])
: this.Query;
this.status = PAGINATE_PENDING;
return runQuery
.limitToLast(this.limit)
.get()
.then((QuerySnapshot) => {
this.status = PAGINATE_UPDATED;
//*IF* documents (i.e. haven't gone back ebfore start)
if (!QuerySnapshot.empty) {
//then update document set, and execute callback
this.snapshot = QuerySnapshot;
}
return this.snapshot.docs.map((doc) => {
return {
...doc.data(),
Id: doc.id,
ref: doc.ref
};
});
});
};
}
/**
* ----------------------------------------------------------------------
* @function filterQuery
* builds and returns a query built from an array of filter (i.e. "where")
* consitions
* @param {Query} query collectionReference or Query to build filter upong
* @param {array} filterArray an (optional) 3xn array of filter(i.e. "where") conditions
* @returns Firestor Query object
*/
export const filterQuery = (query, filterArray = null) => {
return filterArray
? filterArray.reduce((accQuery, filter) => {
return accQuery.where(filter.fieldRef, filter.opStr, filter.value);
}, query)
: query;
};
/**
* ----------------------------------------------------------------------
* @function sortQuery
* builds and returns a query built from an array of filter (i.e. "where")
* consitions
* @param {Query} query collectionReference or Query to build filter upong
* @param {array} sortArray an (optional) 2xn array of sort (i.e. "orderBy") conditions
* @returns Firestor Query object
*/
export const sortQuery = (query, sortArray = null) => {
return sortArray
? sortArray.reduce((accQuery, sortEntry) => {
return accQuery.orderBy(sortEntry.fieldRef, sortEntry.dirStr || "asc");
//note "||" - if dirStr is not present(i.e. falsy) default to "asc"
}, query)
: query;
};
I also have the equivalent for CollectionGroup queries, and listeners for each as well.