CSFrequency/react-firebase-hooks

Pagination Example

iamthefox opened this issue ยท 23 comments

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

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...

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.

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

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

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

Anyone ever get a pagination example working?

@kriss1897

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.

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

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

any update from react-hooks contribute?

any update from react-hooks contribute?

i'm looking for that too

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}/>);

Any updates on this feature?

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

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) => {
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;