CSFrequency / react-firebase-hooks

React Hooks for Firebase.
Apache License 2.0
3.6k stars 306 forks source link

Pagination Example #13

Open iamthefox opened 5 years ago

iamthefox commented 5 years ago

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

chrisbianca commented 5 years ago

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

iamthefox commented 5 years ago

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.

kriss1897 commented 5 years ago

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

chrisbianca commented 5 years ago

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

kriss1897 commented 5 years ago

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

stevecastaneda commented 5 years ago

Anyone ever get a pagination example working?

@kriss1897

ebemunk commented 4 years ago

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:

would love to know how you guys are approaching it.

RobertSasak commented 4 years ago

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

optilude commented 4 years ago

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

robsonkades commented 4 years ago

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

dsvgit commented 4 years ago

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

minhchienwikipedia commented 3 years ago

any update from react-hooks contribute?

reanyouda commented 3 years ago

any update from react-hooks contribute?

i'm looking for that too

achuinard commented 3 years ago

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}/>);
ghost commented 2 years ago

Any updates on this feature?

neilor commented 2 years ago

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

Kibadango13 commented 2 years ago

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;

michaelpomogajko commented 4 months ago

Any updates on this? The given examples are great, but lack onSnapshot behaviour. I'm trying to create a chat with that, so definitely need pagination, but also the onSnapshot behavior to live stream new messages, any ideas?

jamesofoaye commented 1 week ago
import { useState, useRef, useEffect, useCallback } from "react";
import { collection, query, orderBy, startAfter, limit, getCountFromServer, DocumentSnapshot, QueryConstraint } from "firebase/firestore";
import { useCollectionData } from "react-firebase-hooks/firestore";
import { db } from "@/library/firebase/client";
import { DB_COLLECTIONS } from "@/library/constants";
import { createFirestoreDataConverter } from "@/library/firebase/helpers";
import { InvoiceTypes } from "@/library/@types/invoice";

const invoiceConverter = createFirestoreDataConverter<InvoiceTypes>();
const invoicesRef = collection(db, DB_COLLECTIONS.INVOICES);
const PAGE_SIZE = 6;

const useFetchInvoices = (cursor: DocumentSnapshot | undefined, per_page: number) => {
  const constraints: QueryConstraint[] = [orderBy("created_at", "desc"), limit(per_page)];
  if (cursor) constraints.push(startAfter(cursor));
  const invoicesQuery = query(invoicesRef, ...constraints).withConverter(invoiceConverter);
  return useCollectionData(invoicesQuery, { snapshotListenOptions: { includeMetadataChanges: true } });
};

type UsePaginatedInvoicesReturnType = {
  meta: {
    total: number;
    total_pages: number;
    current_page: number;
    per_page: number;
  };
  invoices: InvoiceTypes[];
  loading: boolean;
  onPageChanged: (nextPage: number) => void;
  canGoBack: boolean;
  canGoNext: boolean;
};
/**
 * Custom hook to manage paginated invoices.
 *
 * This hook provides functionality to fetch and manage paginated invoices,
 * including metadata such as total count, total pages, current page, and
 * items per page. It also handles pagination state and provides methods
 * to navigate between pages.
 *
 * @returns {UsePaginatedInvoicesReturnType} An object containing:
 * - `meta`: Metadata about the pagination state, including total count, total pages, current page, and items per page.
 * - `invoices`: The current list of invoices for the current page.
 * - `loading`: A boolean indicating whether the invoices are currently being loaded.
 * - `onPageChanged`: A callback function to change the current page.
 * - `canGoBack`: A boolean indicating whether the user can navigate to the previous page.
 * - `canGoNext`: A boolean indicating whether the user can navigate to the next page.
 */
const usePaginatedInvoices = (): UsePaginatedInvoicesReturnType => {
  const [meta, setMeta] = useState({ total: 0, total_pages: 0, current_page: 1, per_page: PAGE_SIZE });
  const cursors = useRef<Map<number, DocumentSnapshot>>(new Map());
  const [invoices = [], loading, _error, snapshot] = useFetchInvoices(cursors.current.get(meta.current_page), meta.per_page);

  useEffect(() => {
    getCountFromServer(query(invoicesRef)).then((result) => {
      const count = result.data().count;
      setMeta((meta) => ({ ...meta, total: count, total_pages: Math.ceil(count / meta.per_page) }));
    });
  }, []);

  const onPageChanged = useCallback((nextPage: number) => {
    setMeta((meta) => {
      cursors.current.set(meta.current_page + 1, snapshot?.docs[snapshot.docs.length - 1] ?? ({} as DocumentSnapshot));
      return { ...meta, current_page: nextPage };
    });
  }, [snapshot]);

  const canGoBack = meta.current_page > 1;
  const canGoNext = meta.current_page < meta.total_pages;

  return { meta, invoices, loading, onPageChanged, canGoBack, canGoNext };
};

export { usePaginatedInvoices };

Usage

const InvoicePagination = () => {
  const { meta, onPageChanged, canGoBack, canGoNext } = usePaginatedInvoices();
  return (
    <Pagination className="flex items-center space-x-2">
      <PaginationContent>
        <PaginationItem>
          <PaginationPrevious disabled={!canGoBack} onClick={() => onPageChanged(meta.current_page - 1)}>
            Previous
          </PaginationPrevious>
        </PaginationItem>
        <PaginationItem>{meta.current_page}</PaginationItem>
        <PaginationItem>of {meta.total_pages}</PaginationItem>
        <PaginationItem>
          <PaginationNext disabled={!canGoNext} onClick={() => onPageChanged(meta.current_page + 1)}>
            Next
          </PaginationNext>
        </PaginationItem>
      </PaginationContent>
    </Pagination>
  );
}
export default InvoicePagination;
RobertSasak commented 1 week ago

Well done, please add ts after ticks to highlight your code.

```ts
// here comes code

https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting
jamesofoaye commented 1 week ago

Done

Well done, please add ts after ticks to highlight your code.

```ts
// here comes code

https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks#syntax-highlighting
RobertSasak commented 1 week ago

Cool. I forgot to mention tsx option for files that also contain HTML.

jamesofoaye commented 1 week ago

Done

Cool. I forgot to mention tsx option for files that also contain HTML.