React.js usePagination Hook

Custom React.js hook used for client-side pagination.

React.js usePagination Hook

import { useState } from "react";

const usePagination = (itemsPerPage: number, totalItems: number) => {
  const totalPages = Math.ceil(totalItems / itemsPerPage);

  const [startPageIndex, setStartPageIndex] = useState(0);
  const [endPageIndex, setEndPageIndex] = useState(itemsPerPage - 1);
  const [currentPage, setCurrentPage] = useState(1);
  const dispPage = (pageNo: number) => {
    setCurrentPage(pageNo);

    let start_page_index = itemsPerPage * pageNo - itemsPerPage;
    let end_page_index = itemsPerPage * pageNo - 1;

    setStartPageIndex(start_page_index);

    if (end_page_index > totalItems) {
      setEndPageIndex(totalItems - 1);
    } else {
      setEndPageIndex(end_page_index);
    }
  };

  return [
    totalPages,
    startPageIndex,
    setStartPageIndex,
    endPageIndex,
    setEndPageIndex,
    currentPage,
    dispPage,
    setCurrentPage,
  ] as const;
};

export default usePagination;

Usage

import { useEffect, useRef, useState, useMemo } from "react";
import usePagination from "./hooks/usePagination";

const PAGINATION_ITEMS_PER_PAGE = 12;

export default function App() {
  const [
    totalPages,
    startPageIndex,
    setStartPageIndex,
    endPageIndex,
    setEndPageIndex,
    currentPage,
    setCurrentPage,
    dispPage,
  ] = usePagination(PAGINATION_ITEMS_PER_PAGE, items.length);

  const [maxPageNumberLimit, setMaxPageNumberLimit] = useState(5);
  const [minPageNumberLimit, setMinPageNumberLimit] = useState(0);

  const pages = [];
  for (let i = 1; i <= Math.ceil(productsLength / itemsPerPage); i++) {
    pages.push(i);
  }

  const handleNextbtn = () => {
    setCurrentPage(currentPage + 1);

    if (currentPage + 1 > maxPageNumberLimit) {
      setMaxPageNumberLimit(maxPageNumberLimit + 5);
      setMinPageNumberLimit(minPageNumberLimit + 5);
    }
    window.scrollTo({
      behavior: "smooth",
      top: scrollToRef.current.offsetTop,
    });
  };

  const handleDotButton = (pageNum: number) => {
    setCurrentPage(pageNum);

    if (pageNum + 1 > maxPageNumberLimit) {
      setMaxPageNumberLimit(maxPageNumberLimit + 5);
      setMinPageNumberLimit(minPageNumberLimit + 5);
    }
    window.scrollTo({
      behavior: "smooth",
      top: scrollToRef.current.offsetTop,
    });
  };

  const handlePrevbtn = () => {
    setCurrentPage(currentPage - 1);

    if ((currentPage - 1) % 5 == 0) {
      setMaxPageNumberLimit(maxPageNumberLimit - 5);
      setMinPageNumberLimit(minPageNumberLimit - 5);
    }
    window.scrollTo({
      behavior: "smooth",
      top: scrollToRef.current.offsetTop,
    });
  };

  return (
    <div className="flex justify-center">
      <div className="flex items-center justify-center text-center text-white">
        <button
          onClick={handlePrevbtn}
          disabled={currentPage == pages[0] ? true : false}
          className={
            currentPage == pages[0]
              ? "h-10 w-10 rounded-full bg-gray cursor-not-allowed flex justify-center items-center"
              : "h-10 w-10 rounded-full bg-green text-white  flex justify-center items-center"
          }
        >
          Prev page
        </button>
      </div>
      <ul className="flex justify-center mx-5 mt-2 xxxs:mx-10">
        {pages.map((number: number) => {
          // if (number < maxPageNumberLimit + 1 && number > minPageNumberLimit)
          // Only really need to render nothing if there isnt any data, the map will render the correct amount
          // of dots and the pagination button being disabled will stop going to a page that doesnt exist.
          if (number < maxPageNumberLimit + 1) {
            return (
              <li
                key={number}
                id={number.toString()}
                className="self-center mx-2"
                onClick={() => handleDotButton(number)}
              >
                <button
                  className={
                    currentPage == number
                      ? "h-5 w-5 rounded-full border-green-500 opacity-50 bg-green-700 text-white"
                      : "h-5 w-5 rounded-full border-green-500 bg-white text-white"
                  }
                ></button>
              </li>
            );
          } else {
            return null;
          }
        })}
      </ul>
      <div className="flex items-center justify-center text-center text-white">
        <button
          onClick={handleNextbtn}
          disabled={currentPage == pages[pages.length - 1] ? true : false}
          className={
            currentPage == pages[pages.length - 1]
              ? "h-10 w-10 rounded-full bg-gray cursor-not-allowed flex justify-center items-center"
              : "h-10 w-10 rounded-full bg-gray cursor-not-allowed flex justify-center items-center"
          }
        >
          Next page
        </button>
      </div>
    </div>
  );
}