Skip to content

Instantly share code, notes, and snippets.

@hisui
Last active August 12, 2024 04:14
Show Gist options
  • Save hisui/6602d6cdf972abb3216bdcbb2d8d80a5 to your computer and use it in GitHub Desktop.
Save hisui/6602d6cdf972abb3216bdcbb2d8d80a5 to your computer and use it in GitHub Desktop.
Pagination for Firestore
import React, { ReactElement } from 'react'
import { Link, useSearchParams } from 'react-router-dom'
import { collection, getFirestore, orderBy } from 'firebase/firestore'
import { usePagedSnapshot } from './firestore-paging'
const comments = collection(getFirestore(), "comments")
const order = orderBy("createdAt", "desc")
export function CommentList(): ReactElement {
const [params] = useSearchParams()
const start = params.get("start") ?? undefined
const until = params.get("until") ?? undefined
const page = usePagedSnapshot(comments, order, { start, until, count: 10 })
return <div>{
page === undefined ? <>Loading..</> : <>
<div>{ page.prev && <Link to={`?until=${page.prev}`}>Prev</Link>}</div>
<div>{ page.next && <Link to={`?start=${page.next}`}>Next</Link>}</div>
<ul>
{page.docs.map(doc => <li>{doc.id}</li>)}
</ul>
</>
}</div>
}
import { CollectionReference, DocumentData, DocumentSnapshot, Query, QueryConstraint, QueryDocumentSnapshot, QuerySnapshot, doc, endBefore, getDoc, limit, limitToLast, onSnapshot, query, startAt } from "firebase/firestore"
import { useEffect, useMemo, useState } from "react"
type Page = {
start?: string
until?: string
count: number
}
export function usePagedSnapshot<T = DocumentData>(
src: CollectionReference<T>,
filter: QueryConstraint,
page: Page,
): void | { docs: QueryDocumentSnapshot<T>[], next?: string, prev?: string } {
const [pivot, setPivot] = useState<DocumentSnapshot<T> | null>()
useEffect(() => {
(async () => {
const id = page.start ?? page.until
setPivot(undefined)
setPivot(id ? await getDoc(doc(src, id)) : null)
}) ()
}, [src, page.start, page.until])
const n = page.count
const pagedQuery = useMemo(() => {
if (pivot !== undefined) {
return query(
src, filter,
... pivot ? page.until
? [endBefore(pivot), limitToLast(n+1)]
: [startAt(pivot), limit(n+1)] : [limit(n+1)],
)
}
}, [pivot, filter, n])
const snapshot = useSnapshot(pagedQuery)
if (snapshot !== undefined) {
const { docs } = snapshot
if (page.until) {
return {
docs: docs.slice(-n),
next: page.until,
prev: docs.length !== n+1 ? undefined: docs[1].id,
}
} else {
return {
docs: docs.slice(0, n),
prev: page.start,
next: docs.length !== n+1 ? undefined: docs[n].id,
}
}
}
}
export function useSnapshot<T = DocumentData>(query?: Query<T>): void | QuerySnapshot<T> {
const [result, setResult] = useState<QuerySnapshot<T>>()
useEffect(() => {
setResult(undefined)
if (query !== undefined) {
return onSnapshot(query, setResult)
}
}, [query])
return result
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment