Fetching my next batch of documents is not working in Firebase V9

Viewed 36

I have a firebase collection with over 200k documents in it. I am trying to query this data in batches of 500. I get the first 500 documents back but I cant get the next batch of documents. Also I would lie when I click the previous button, I get the previous batch. Any help will truly be appreciated. Thank you good people!

Here is my code for the component fetching data:

import React, { useEffect, useState } from "react";

import {
  collection,
  getDocs,
  query,
  startAfter,
  limit,
} from "firebase/firestore";
import { db } from "../../firebase/firebase";

import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import TablePagination from "@mui/material/TablePagination";
import ClearIcon from "@mui/icons-material/Clear";
import CheckIcon from "@mui/icons-material/Check";

import EnhancedTableHead from "./usersTableHead";
import { EnhancedTableToolbar } from "./usersToolbar";

function descendingComparator(a, b, orderBy) {
  if (b[orderBy] < a[orderBy]) {
    return -1;
  }
  if (b[orderBy] > a[orderBy]) {
    return 1;
  }
  return 0;
}

function getComparator(order, orderBy) {
  return order === "desc"
    ? (a, b) => descendingComparator(a, b, orderBy)
    : (a, b) => -descendingComparator(a, b, orderBy);
}

function stableSort(array, comparator) {
  const stabilizedThis = array.map((el, index) => [el, index]);
  stabilizedThis.sort((a, b) => {
    const order = comparator(a[0], b[0]);
    if (order !== 0) {
      return order;
    }
    return a[1] - b[1];
  });
  return stabilizedThis.map((el) => el[0]);
}

export default function Clients() {
  const [rows, setRows] = useState([]); // table rows

  const [order, setOrder] = useState("asc");
  const [orderBy, setOrderBy] = useState("name");
  const [page, setPage] = useState(0);
  const [rowsPerPage, setRowsPerPage] = useState(10);

  // pull data from firebase and set table rows
  useEffect(() => {
    const getUsers = async () => {
      //query the first 500 docs
      const firstUsers = await getDocs(
        query(collection(db, "users"), limit(500))
      );

      // get last visible doc
      const lastVisibleDoc = firstUsers.docs[firstUsers.docs.length - 1];

      firstUsers.forEach((user) => {
        setRows((row) => [
          ...row,
          {
            id: user.data().idNumber,
            registered: user.data().dateRegistered?.toDate().toDateString(),
            firstName: user.data().firstName,
            lastName: user.data().lastName,
            email: user.data().email,
            idNumber: user.data().idNumber,
            phoneNumber: user.data().phoneNumber,
            loanLimit: user.data().loanLimit,
            deviceModel: user.data().deviceModel,
            isQualified: user.data().qualified,
          },
        ]);
      });

      // get the next 500 docs after the last one
      const next = await query(
        collection(db, "users"),
        startAfter(lastVisibleDoc),
        limit(500)
      );

      console.log("next", next);

      next?.forEach((user) => {
        setRows((row) => [
          ...row,
          {
            id: user.data().idNumber,
            registered: user.data().dateRegistered?.toDate().toDateString(),
            firstName: user.data().firstName,
            lastName: user.data().lastName,
            email: user.data().email,
            idNumber: user.data().idNumber,
            phoneNumber: user.data().phoneNumber,
            loanLimit: user.data().loanLimit,
            deviceModel: user.data().deviceModel,
            isQualified: user.data().qualified,
          },
        ]);
      });
    };

    getUsers();
  }, []);

  console.log("users: ", rows);

  const handleRequestSort = (event, property) => {
    const isAsc = orderBy === property && order === "asc";
    setOrder(isAsc ? "desc" : "asc");
    setOrderBy(property);
  };

  const handleChangePage = (event, newPage) => {
    setPage(newPage);
  };

  const handleChangeRowsPerPage = (event) => {
    setRowsPerPage(parseInt(event.target.value, 10));
    setPage(0);
  };

  const emptyRows =
    page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0;

  return (
    <div className="">
      <EnhancedTableToolbar />
      <TableContainer component={Paper}>
        <Table sx={{ minWidth: 750 }}>
          <EnhancedTableHead
            order={order}
            orderBy={orderBy}
            onRequestSort={handleRequestSort}
            rowCount={rows.length}
          />
          <TableBody>
            {stableSort(rows, getComparator(order, orderBy))
              .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
              .map((row, index) => (
                <TableRow
                  hover
                  key={index}
                  sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
                  tabIndex={-1}
                >
                  <TableCell align="left">{row.registered}</TableCell>
                  <TableCell align="left">{row.firstName}</TableCell>
                  <TableCell align="left">{row.lastName}</TableCell>
                  <TableCell align="left">{row.email}</TableCell>
                  <TableCell align="left">{row.idNumber}</TableCell>
                  <TableCell align="left">{row.phoneNumber}</TableCell>
                  <TableCell align="left">{row.loanLimit}</TableCell>
                  <TableCell align="left">
                    {row.deviceModel === undefined
                      ? "Not Provided"
                      : row.deviceModel}
                  </TableCell>
                  <TableCell align="left">
                    {row.isQualified ? <CheckIcon /> : <ClearIcon />}
                  </TableCell>
                </TableRow>
              ))}
          </TableBody>
        </Table>
      </TableContainer>
      <TablePagination
        rowsPerPageOptions={[5, 10, 25, 50, 100]}
        component="div"
        count={rows.length}
        rowsPerPage={rowsPerPage}
        page={page}
        onPageChange={handleChangePage}
        onRowsPerPageChange={handleChangeRowsPerPage}
      />
    </div>
  );
}
1 Answers

Your lastVisibleDoc should persist outside of your getUsers function. To get pagination working as you expect, you could use an array as a stack to push and pop the last document of each batch and use it as your cursor.

Your state could look something like:

const pageDocuments = useRef([]);

When paging backwards through the collection, you'd want to pop from the array to get the last document used. If the array is empty, you can omit specifying a cursor when querying:

const lastPageDocument = this.pageDocuments.pop();

const next = await query(
    collection(db, "users"),
    lastPageDocument ? startAfter(lastPageDocument) : null,
    limit(500)
  );

When paging forward through the collection, append the last document from the collection snapshot to the array. If you've reached the end of your collection, just skip the call:

const lastDocument = next.docs[next.length - 1];

if(lastDocument) {
    this.pageDocuments.push(lastDocument);
}

This is just a general idea, so you may need to tweak it depending on your needs.

Related