react-firebase-hooks icon indicating copy to clipboard operation
react-firebase-hooks copied to clipboard

Pagination Example

Open iamthefox opened this issue 5 years ago • 18 comments

Question: is there any way to paginate through firebase list using this library?

iamthefox avatar Apr 03 '19 03:04 iamthefox

At the moment there isn't, but this does seem like a very good use case.

Do you have any thoughts on what this might look like from an API point of view?

I will have a think, and put together a plan, but welcome your suggestions...

chrisbianca avatar Apr 03 '19 07:04 chrisbianca

I will look into it and attempt to come up with something.

Did a bit of a research and firebase queries are very limiting in what can be done.

https://github.com/deltaepsilon/firebase-paginator/blob/master/firebase-paginator.js

^ Above would probably be the most comprehensive library that somewhat solved this issue.

iamthefox avatar Apr 04 '19 05:04 iamthefox

I used hooks to filter data from firestore, we can extend that to implement pagination. Should I raise a PR for that?

kriss1897 avatar Apr 22 '19 10:04 kriss1897

@kriss1897 Yes, please do - I'd be interested to see how you've approached it...

chrisbianca avatar Apr 23 '19 14:04 chrisbianca

@chrisbianca. Okay. Give me a few days.. I'll work on it this weekend.

kriss1897 avatar Apr 24 '19 06:04 kriss1897

Anyone ever get a pagination example working?

@kriss1897

stevecastaneda avatar Sep 17 '19 21:09 stevecastaneda

i wrote a custom hook to do "Load More" type of pagination. i'm sure it can use some tweaking from more clever folks here, but it works fine for my purposes for now.

import { useState, useEffect, useCallback } from 'react'
import { useCollectionOnce } from 'react-firebase-hooks/firestore'

const useFirestoreLoadMore = queryFn => {
  const [query, setQuery] = useState(null)
  const [last, setLast] = useState(null)
  const [data, setData] = useState([])

  const [qData, loading, error] = useCollectionOnce(query)

  useEffect(() => {
    setData([])
    setQuery(queryFn())
  }, [queryFn])

  useEffect(() => {
    if (qData && qData.query.isEqual(query)) {
      setLast(qData.docs[qData.docs.length - 1])
      setData([...data, ...qData.docs])
    }
  }, [qData])

  const more = useCallback(() => {
    setQuery(queryFn().startAfter(last))
  }, [queryFn, setQuery, last])

  return [[data, loading, error], more]
}

export default useFirestoreLoadMore

you would use it like this:

const MyComponent = () => {
  const queryFn = React.useCallback(() => {
    let q = firebase
      .firestore()
      .collection('things')
      .orderBy('date', 'desc')
      .limit(15)

    if (maybeYouWantToChangeSomething) {
      q = q.where('something', 'array-contains', maybeYouWantToChangeSomething)
    }

    return q
  }, [maybeYouWantToChangeSomething])

  const [[things, loading, error], more] = useFirestoreLoadMore(queryFn)

  return (
    <div>
      {things.map(thing => <Thing key={thing.id} data={thing} />)}
      <button onClick={() => more()}>Load More</button>
    </div>
  )
}

i hope this is useful to someone.

now for Previous/Next pagination i've run into some roadblocks:

  • i tried tracking first and last snapshot, hoping to use query.startAfter(last) for "Next" and query.endBefore(first) for "Previous" but endBefore doesn't work the way you think combined with limit(). seems you have to track all first snapshots for every page and use something like query.startAfter(firstOfPreviousPage) for "Previous"
  • i can't think of a way to know there is no more "Previous" pages other than tracking a separate page state (i.e. disable "Previous" if page === 0).
  • not sure how to know we've reached the last page, it seems you need to try to load the next page, and if no results (or less than your limit) then that's the last page. seems like a lot of logic and i feel there might be a simpler way

would love to know how you guys are approaching it.

ebemunk avatar Jan 10 '20 08:01 ebemunk

Another attempt https://github.com/bmcmahen/firestore-pagination-hook

RobertSasak avatar Jan 28 '20 16:01 RobertSasak

Can confirm that @RobertSasak's package works well. Thanks!

optilude avatar May 15 '20 19:05 optilude

I created an example of hook with react.js for pagination https://gist.github.com/robsonkades/0f256ab05944699b831031c7e6a8aa84

robsonkades avatar Jul 24 '20 02:07 robsonkades

I found this one https://github.com/kodai3/react-firebase-pagination-hooks

dsvgit avatar Oct 15 '20 13:10 dsvgit

any update from react-hooks contribute?

minhchienwikipedia avatar Mar 19 '21 14:03 minhchienwikipedia

any update from react-hooks contribute?

i'm looking for that too

reanyouda avatar Aug 06 '21 04:08 reanyouda

I actually whipped this up before having seen this Github page, and I came here to share my snippet, but those other hooks look good too.

Here's what I came up with:

import { FirebaseFirestoreTypes } from "@react-native-firebase/firestore";
import { useCallback, useEffect, useMemo, useState } from "react";

const PAGE_SIZE = 20;

export const useCollectionDataStatic = (query: FirebaseFirestoreTypes.Query) => {
  const [data, setData] = useState([]);
  const [fetching, setFetching] = useState(false);

  const dataVals = useMemo(() => {
    return data.map(doc => doc.data());
  }, [data])

  const loadMore = useCallback(() => {
    if (fetching) {
      return;
    }
    if (data.length === 0) {
      setFetching(true);
      query.limit(PAGE_SIZE).get().then(result => {
        setData([...result.docs])
      }).catch(err => console.log(err)).finally(() => setFetching(false));
    } else {
      setFetching(true);
      query.startAfter(data[data.length - 1]).limit(PAGE_SIZE).get().then(result => {
        setData([...data, ...result.docs]);
      }).catch(err => console.log(err)).finally(() => setFetching(false));
    }
  }, [data, dataVals, fetching]);

  useEffect(() => {
    loadMore();
  }, []);

  return [data, dataVals, loadMore];
};

Then it was pretty easy to use it:

const [invoices, invoiceDatas, loadMoreInvoices] = useCollectionDataStatic(query);
  
  return (<FlatInvoiceList invoiceList={invoiceDatas} onEndReached={loadMoreInvoices}/>);

achuinard avatar Aug 26 '21 03:08 achuinard

Any updates on this feature?

ghost avatar Mar 06 '22 13:03 ghost

I will start to contribute here with the pagination @chrisbianca. Anyone here knows if already have some progress about that?

I have a lot of experience with firebase realtime database, with complex queries, compound indexes, "rules and typescript synchronization" and pagination. I think that pagination will have similar Params Interface between realtime database and firestore. So, I can start by doing pagination.

@chrisbianca Do you want to help me with prioritization here (rtdb or firestore or to launch both together)?

PS: I want to share my personal progress through this library to reward the open-source community, especially that. I've created an Issue with my idea explained #219

neilor avatar Mar 09 '22 14:03 neilor

Hello !!!!!!! :). i made my own hook

Componente

const queryFn = useCallback(() => { const q = query( collection(fireStore, "admin_news"), limit(4), orderBy("categoryValue") ); return q; }, []);

const { more, isLoading, data } = usePaginateQuery(queryFn);


Create hook

import { useState, useEffect, useCallback, useRef, useMemo } from "react"; import { DocumentData, Query } from "firebase/firestore"; import { startAfter, query, getDocs } from "firebase/firestore";

const usePaginateQuery = (queryFn: () => Query<DocumentData>) => { const [data, setData] = useState([]); const isMountedRef = useRef(false); const lastItemRef = useRef(null); const [isLoading, setisLoading] = useState(); const [errorMsg, setErrorMsg] = useState();

const resetStates = () => { setData(null); setData([]); setisLoading(false); };

useEffect(() => { if (isMountedRef.current === true) return;

async function fetchQuery() {
  try {
    isMountedRef.current = true;
    setisLoading(true);
    const q = query(queryFn());
    const querySnapshot = await getDocs(q);
    setData([...querySnapshot.docs]);
    lastItemRef.current = querySnapshot.docs[querySnapshot.docs.length - 1];
    setisLoading(false);
  } catch (error) {
    resetStates();
    setErrorMsg(error.code);
  }
}
fetchQuery();

}, [queryFn]);

const more = useCallback(async () => { try { setisLoading(true); const next = query(queryFn(), startAfter(lastItemRef.current)); const querySnapshot = await getDocs(next); setData([...data, ...querySnapshot.docs]); lastItemRef.current = querySnapshot.docs[querySnapshot.docs.length - 1]; setisLoading(false); } catch (error) { resetStates(); setErrorMsg(error.code); } }, [data, queryFn]);

return useMemo( () => ({ more, isLoading, data, errorMsg, }), [more, isLoading, data, errorMsg] ); };

export default usePaginateQuery;

Kibadango13 avatar Jun 10 '22 14:06 Kibadango13