Paginating Firestore Queries with Document Snapshot Cursors and react-redux-firebase

We’ve found react-redux-firebase (RRF) to be a big time saver when querying Firestore. Despite the name, the library has robust support for not just Firebase, but Firestore too, pulling in redux-firestore behind the scenes for Firestore operations. The documentation for RRF’s use of redux-firestore’s APIs is scant within the RRF docs themselves, but redux-firestore’s documentation is extensive and can be applied to usage of RRF.

Paginating Firestore Queries react-redux-firebase

 

We make use of paginated Firestore queries to infinitely scroll a particularly long list of items. Pagination in Firestore is accomplished through a set of query configuration options that use cursors to determine where to start or end the page of your query. Here’s what redux-firestore’s docs have to say about using a cursor clause to paginate a Firestore query:

STARTAT

Creates a new query where the results start at the provided document (inclusive)

{
collection: ‘cities’,
orderBy: ‘population’,
startAt: 1000000
},

Can only be used with collections. Types can be a string, number, Date object, or an array of these types, but not a Firestore Document Snapshot

The query configuration looks as we’d expect it to, but what about that line in italics? Firestore does allow Document Snapshots as query cursors, but redux-firestore’s docs say that we can’t use them.

That’s a big problem. If we can only supply a simple field value query cursor, what happens if that field value isn’t guaranteed to be unique across documents? We’ll risk adding duplicates if we use startAt, and we’ll risk missing documents if we use startAfter. The former will be an annoyance, but the later is an unacceptable compromise. If we could instead use a Document Snapshot, Firestore will know exactly where to begin our paginated query. Will we have to manually create this query ourselves, dealing with the extra task of setting our data in the store?

The surprising answer is no: because the docs are wrong. redux-firestore does allow Document Snapshots as query cursors. But now we’re left with a bit of a head scratcher – because we’re using redux-firestore to automate fetching data with a Firestore query and update our redux store for us, we don’t have access to the Document Snapshot we’d need to set our cursor.

But there’s another surprise here: we do have access to that Document Snapshot. redux-firestore v0.11.0 introduced an undocumented feature to the library called the Snapshot Cache. Here’s what the PR description says, from developer illuminist:

Get DocumentSnapshot and QuerySnapshot with object from either data or ordered firestore state. If provided with doc data, it will return DocumentSnapshot, providing with a collection from data or an array from ordered state will return QuerySnapshot, except ordered state that generated as DocumentRef will return DocumentSnapshot. Note: the cache is local and, not persistance. Passing an object from initial state or from SSR state will yield undefined.

This sounds like exactly what we’d need to properly paginate our query. So how do we retrieve the Document Snapshot we need from the Snapshot Cache? This feature includes a function called getSnapshotByObject, which takes as its parameter a document object from state.firestore.ordered or state.firestore.data.

Here’s how this looks in practice, using hooks:

import { getSnapshotByObject } from 'redux-firestore';
 
const [cursor, setCursor] = useState(null)
 
const items = useSelector(state => state.firestore.ordered.items)
 
const updateCursor = () => {
  setCursor(getSnapshotByObject(items[items.length - 1]))
}

And here’s our query:

useFirestoreConnect([{
  collection: 'items',
  orderBy: 'name',
  ...(cursor && { startAfter: cursor })
}])

Precaution should be taken to make sure that items is loaded before calling updateCursor, which can either be called on click, or placed in a useEffect for automatically requerying based on a scroll trigger. In our use case, we build an object of cursors, with a cursor entry being fed to the next query down the line as we connect each page of the query individually for an infinite scroll effect. But for more simple UIs, a solution this concise will work just fine.

Featured Posts

Follow Along

Stay up to date with the latest news & examples from SeedCode

Leave a Reply

Your email address will not be published. Required fields are marked *

Check out some of our other posts ...

Suggesting Appointment Slots

Show Available Slots that Match Multiple Criteria Schedulers often look for gaps in their schedules to find the open resources for each opportunity. But sometimes,

Introducing Draft Settings Mode

Following up on this idea that people stretch themselves when they feel a little safer, we’ve been very focused on the customization experience for DayBack

New Longer Timescales for DayBack

Resource Scheduling Swimlanes You can now extend the pivoted scheduling view in DayBack to show items by week instead of solely by day. This lets

FileMaker Summer Camp – Recap

Unconference Sessions If you missed Pause in October, here’s a look at the sessions that attendees hosted. All the sessions are listed in this post

COMPANY

FOLLOW ALONG

Stay up to date with the latest news & examples from SeedCode

© 2024 SeedCode, Inc.