Using Pagination With Firestore

Function

  import React, { useState, useEffect } from "react";
  import {
  collection,
  endBefore,
  getDocs,
  limit,
  limitToLast,
  orderBy,
  query,
  startAfter,
} from "firebase/firestore";
import { db } from "../../../Configs/firebase";

function AdminAppTableUser() {  
  // useState
  const [userData, setUserData] = useState([]);
  const [getQuery, setGetQuery] = useState([]);
  const [first, setFirst] = useState([]);
  
  const [checkFirst, setCheckFirst] = useState("");
  const [checkEmail, setCheckEmail] = useState("");

  // getData Function
  // status: next/prev, value:first/last
  const getUserData = async (status, value) => { 
    try {
      // get next data
      if (status === "next") {
        let arr = [];
        const q = query(
          collection(db, "users"),
          orderBy("name_user", "asc"),
          startAfter(value),
          limit(10)
        );
        const querySnapshot = await getDocs(q);

        const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
        const firstVisible = querySnapshot.docs[querySnapshot.docs.length - 10];

        setFirst(firstVisible);
        console.log("next first", first);

        setGetQuery(lastVisible);
        console.log("next last", getQuery);

        querySnapshot.forEach((doc) => {
          arr.push(doc.data());
        });

        setUserData(arr);
        setCheckEmail(arr[0].email_user);
        return arr;
      }

      // get previous data
      if (status === "prev") {
        let arr = [];

        const q = query(
          collection(db, "users"),
          orderBy("name_user", "asc"),
          endBefore(value),
          limitToLast(10)
        );
        const querySnapshot = await getDocs(q);

        const lastVisible2 = querySnapshot.docs[querySnapshot.docs.length - 1];
        const firstVisible2 =
          querySnapshot.docs[querySnapshot.docs.length - 10];

        setFirst(firstVisible2);
        console.log("prev first", firstVisible2);

        setGetQuery(lastVisible2);
        console.log("prev last", lastVisible2);

        querySnapshot.forEach((doc) => {
          arr.push(doc.data());
        });

        setUserData(arr);
        setCheckEmail(arr[0].email_user);

        return arr;
      }

      // get first data
      let arr = [];
      const q = query(
        collection(db, "users"),
        orderBy("name_user", "asc"),
        limit(10)
      );
      const querySnapshot = await getDocs(q);

      const lastVisible3 = querySnapshot.docs[querySnapshot.docs.length - 1];
      const firstVisible3 = querySnapshot.docs[querySnapshot.docs.length - 10];
      querySnapshot.forEach((doc) => {
        arr.push(doc.data());
      });

      setFirst(firstVisible3);
      setGetQuery(lastVisible3);
      setCheckFirst(arr[0].email_user);

      setUserData(arr);
      setCheckEmail(arr[0].email_user);

      return arr;
    } catch (error) {
      console.log(error);
    }
  };
  
  return ( . . . .. . . )
  }

there are three step in this function

Step 1 - Checking if Next Status is thrown

Step 2 - Checking if Prev Status is thrown

Step 3 - If no status is thrown

Components

<ButtonGroup
    spacing="3"
    justifyContent="space-between"
    width={{ base: "full", md: "auto" }}
    variant="secondary"
    >
      // check if the first array data in collection are 
      // the same with the first data in current userData array
      // hide the prev button if the data are exactly the same
      {checkFirst === checkEmail ? (
        <></>
        ) : (
        <Button onClick={() => getUserData("prev", first)}>
        Previous
        </Button>
      )}

      //check userData.length. if less than limit, hide the next button
      {userData.length !== 10 ? (
        <></>
        ) : (
        <Button onClick={() => getUserData("next", getQuery)}>
          Next
        </Button>
      )}
</ButtonGroup>

To fully understand the flow, the explanation will start from the third step, because the third step will load first when we access the page for the first time. let's start shall we

Getting initial data

      // creating arr
      let arr = [];
      
      // query data from firestore
      const q = query(
        collection(db, "users"), // define collection
        orderBy("name_user", "asc"), // set data order by field and index
        limit(10) // limit the data being called from database
      );
      
      // get data
      const querySnapshot = await getDocs(q);
      
      // get last doc from query
      const lastVisible3 = querySnapshot.docs[querySnapshot.docs.length - 1];
      
      //get first doc from query
      const firstVisible3 = querySnapshot.docs[querySnapshot.docs.length - 10];
      
      // push data from query to arr
      querySnapshot.forEach((doc) => {
        arr.push(doc.data());
      });

      // set first data from curent query
      setFirst(firstVisible3);
      
      // set last data from curent query
      setGetQuery(lastVisible3);
      
      // set data from the very first doc in the very first query 
      // for pagination condition purpose
      setCheckFirst(arr[0].email_user);

      // set data to be use for showing the data into the table
      setUserData(arr);
      
      // set data checker from the first doc in curent query
      // for pagination condition purpose
      setCheckEmail(arr[0].email_user);

      // return the data from the function
      return arr;

is it confusing? well, if isn't, you can carry on into the next step. But, if you are confuse, let's talk a little bit more about it.

at first, we are preparing object to hold the data that we're gonna get from the database. latter on, this object will be used to show the data into the table.

 let arr = [];

after that, we make data query from firestore collection

const q = query(
 collection(db, "users"), // define collection
 orderBy("name_user", "asc"), // set data order by field and index
 limit(10) // limit the data being called from database
);

// get data
const querySnapshot = await getDocs(q);

here's the important part, we're going to define the first and last doc. where we gonna use both?

// get last doc from query
const lastVisible3 = querySnapshot.docs[querySnapshot.docs.length - 1];
      
//get first doc from query
const firstVisible3 = querySnapshot.docs[querySnapshot.docs.length - 10];

lastVisible will be use to get data for the next page, and firstVisible will be use to get data for the perv page

next, we need to push data that we got from database into arr object that we made earlier

// push data from query to arr
 querySnapshot.forEach((doc) => {
  arr.push(doc.data());
 });

then, we set the data that we have into state

// set first data from curent query
setFirst(firstVisible3);
      
// set last data from curent query
setGetQuery(lastVisible3);
      
// set data from the very first doc in the very first query 
// for pagination condition purpose
setCheckFirst(arr[0].email_user);

// set data to be use for showing the data into the table
setUserData(arr);
      
// set data checker from the first doc in curent query
// for pagination condition purpose
setCheckEmail(arr[0].email_user);

that's it, we have comleted the first step. Next, we will create the condition for the next and prev query.

Next Page

in this step, the first thing we are going to do is checking the status being thrown

if (status === "next") {
. . .

if the status match the condition, it will run the query for the next page that are being load

let arr = [];

const q = query(
 collection(db, "users"),
 orderBy("name_user", "asc"),
 startAfter(value),
 limit(10)
);

const querySnapshot = await getDocs(q);

here, we're gonna use the 2nd parameter that being thrown, value

the rest are pretty the same with before

const lastVisible = querySnapshot.docs[querySnapshot.docs.length - 1];
const firstVisible = querySnapshot.docs[querySnapshot.docs.length - 10];

setFirst(firstVisible);

setGetQuery(lastVisible);

querySnapshot.forEach((doc) => {
 arr.push(doc.data());
});

setUserData(arr);
setCheckEmail(arr[0].email_user);

return arr;

Prev Page

in this step, the first thing we are going to do is checking the status being thrown

// get previous data
if (status === "prev") {
. . .

if the status match the condition, it will run the query for the next page that are being load

let arr = [];

const q = query(
 collection(db, "users"),
 orderBy("name_user", "asc"),
 endBefore(value),
 limit(10)
);

const querySnapshot = await getDocs(q);

here, we're gonna use the 2nd parameter that being thrown, value

the rest are pretty the same with before

Setup Next and Prev Button

import {
  Button,
  ButtonGroup,
} from "@chakra-ui/react";

<ButtonGroup
    spacing="3"
    justifyContent="space-between"
    width={{ base: "full", md: "auto" }}
    variant="secondary"
    >
      // check if the first array data in collection are 
      // the same with the first data in current userData array
      // hide the prev button if the data are exactly the same
      {checkFirst === checkEmail ? (
        <></>
        ) : (
        <Button onClick={() => getUserData("prev", first)}>
        Previous
        </Button>
      )}

      //check userData.length. if less than limit, hide the next button
      {userData.length !== 10 ? (
        <></>
        ) : (
        <Button onClick={() => getUserData("next", getQuery)}>
          Next
        </Button>
      )}
</ButtonGroup>

That's it. Your pagination have been completed

Last updated

Was this helpful?