reactjstypescriptreact-table

Custom pagination - React table


I want to make a custom pagination using react-table. It needs to look quite a specific way:

Currently it looks like this, I guess this is the default for the current pagination I am using:

enter image description here

I need it to look like this:

enter image description here

My code is the following:

import React, { useEffect, useState } from 'react';
import { useTable, usePagination } from 'react-table';
import { TableProps } from '../../../types';

export const Table: React.FC<TableProps> = ({
  columns,
  data,
  isLoading,
  hasPagination = true,
  pageNumber,
  onPageChange,
  maxPage,
  onRowClick = () => undefined,
  maxCount,
}) => {
  const [canPreviousPage, setCanPreviousPage] = useState(false);
  const [canNextPage, setCanNextPage] = useState(false);
  const {
    getTableProps,
    getTableBodyProps,
    headerGroups,
    prepareRow,
    page,
    pageCount,
    nextPage,
    previousPage,
  } = useTable(
    {
      columns,
      data,
      manualPagination: true,
      pageCount: maxPage,
    },
    usePagination,
  );

  useEffect(() => {
    if (pageNumber <= 1) {
      setCanPreviousPage(false);
    } else {
      setCanPreviousPage(true);
    }
    if (maxPage > pageNumber) {
      setCanNextPage(true);
    } else {
      setCanNextPage(false);
    }
  }, [pageNumber, maxPage]);

  const onNextPage = () => {
    nextPage();
    onPageChange(pageNumber + 1);
  };

  const onPreviousPage = () => {
    previousPage();
    onPageChange(pageNumber - 1);
  };

  const Pagination = () => {
    if (!hasPagination) {
      return null;
    }

    return (
      <div className="pagination flex items-center text-black mt-3">
        <Button onClick={onPreviousPage} disabled={!canPreviousPage}>
          <Icon className={canPreviousPage ? 'color-black' : 'color-grey-200'}>chevron_left</Icon>
        </Button>
        <span>
          Page{' '}
          <strong>
            {pageNumber} of {pageCount}
          </strong>
        </span>
        <Button onClick={onNextPage} disabled={!canNextPage}>
          <Icon className={canNextPage ? 'color-black' : 'color-grey-200'}>chevron_right</Icon>
        </Button>
        <div>{maxCount} records found</div>
        <div className="mx-1">
          <ActivityLoader isLoading={isLoading} />
        </div>
      </div>
    );
  };

  return (
    <div className="pt-10 pl-20 pr-16 font-nunito font-medium text-base">
      <div className="align-middle inline-block w-full w-40 text-left">
        <div className="shadow border-b border-gray-200">
          <table {...getTableProps} className="w-full divide-y divide-gray-200">
            <thead className="bg-mainBlue p-16">
              {headerGroups.map((headerGroup) => (
                <tr {...headerGroup.getHeaderGroupProps()}>
                  {headerGroup.headers.map((column) => (
                    <th
                      scope="col"
                      className="px-6 py-4 text-left font-medium text-gray-500 p-16 bg-mainBlue text-base"
                      {...column.getHeaderProps()}
                    >
                      {column.render('Header')}
                    </th>
                  ))}
                </tr>
              ))}
            </thead>
            <tbody {...getTableBodyProps()} className="bg-white divide-y divide-gray-200">
              {page.map((row) => {
                prepareRow(row);
                return (
                  <tr {...row.getRowProps()} onClick={() => onRowClick(_.get(row, 'original.id'))}>
                    {row.cells.map((cell) => {
                      return (
                        <td
                          className="px-6 py-4 whitespace-nowrap text-black"
                          {...cell.getCellProps()}
                        >
                          {cell.render('Cell')}
                        </td>
                      );
                    })}
                  </tr>
                );
              })}
            </tbody>
          </table>
        </div>
      </div>
      <Pagination />
    </div>
  );
};

Can anyone help me to customize my pagination?? I have been reading some docs but I am struggling to implement a solution

enter image description here


Solution

  • First, create a new file called usePaginationPages.js

    import { useCallback, useEffect, useMemo, useState } from "react";
    
    export const usePaginationPages = ({ gotoPage, length, pageSize }) => {
      const [currentPage, setCurrentPage] = useState(1);
    
      const totalPages = useMemo(() => {
        return Math.ceil(length / pageSize);
      }, [length, pageSize]);
    
      const canGo = useMemo(() => {
        return {
          next: currentPage < totalPages,
          previous: currentPage - 1 > 0
        };
      }, [currentPage, totalPages]);
    
      // currentPage siblings
      const pages = useMemo(() => {
        const start = Math.floor((currentPage - 1) / 5) * 5;
        const end = start + 5 > totalPages ? totalPages : start + 5;
        return Array.from({ length: end - start }, (_, i) => start + i + 1);
      }, [currentPage, totalPages]);
    
      // programatically call gotoPage when currentPage changes
      useEffect(() => {
        gotoPage(currentPage - 1);
      }, [currentPage, gotoPage]);
    
      // show first page when per page select options changes 
      useEffect(() => {
        if (pageSize) {
          goTo(1);
        }
      }, [pageSize]);
    
      const goTo = (pg) => {
        setCurrentPage(pg);
      };
    
      const goNext = useCallback(() => {
        if (canGo.next) {
          setCurrentPage((prev) => prev + 1);
        }
      }, [canGo]);
    
      const goPrev = useCallback(() => {
        if (canGo.previous) {
          setCurrentPage((prev) => prev - 1);
        }
      }, [canGo]);
    
      return {
        canGo,
        currentPage,
        pages,
        goTo,
        goNext,
        goPrev
      };
    };
    

    Next, create Pagination.js by implementing our usePaginationPages hook

    import { useState, useEffect, memo } from "react";
    import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/solid";
    import { usePaginationPages } from "./usePaginationPages";
    
    function Pagination({ gotoPage, length, pageSize, setPageSize }) {
      const [perPage, setPerPage] = useState(pageSize);
    
      const {
        canGo,
        currentPage,
        pages,
        goTo,
        goNext,
        goPrev
      } = usePaginationPages({
        gotoPage,
        length,
        pageSize
      });
     
      // update pageSize when perPage changes
      useEffect(() => {
        // don't forget set to Number
        setPageSize(Number(perPage));
      }, [perPage, setPageSize]);
    
      return (
        <div className="m-4 flex items-center justify-center">
          <button
            onClick={goPrev}
            disabled={!canGo.previous}
            className="m-1 px-2 py-1 border rounded-md"
          >
            <ChevronLeftIcon className="h-6 w-4 text-blue-500" />
          </button>
          {pages.map((page, i) => (
            <button
              onClick={() => goTo(page)}
              key={i}
              style={{
                background: currentPage === page ? "blue" : "none",
                color: currentPage === page ? "white" : "black"
              }}
              className="m-1 px-3 py-1 border rounded-md"
            >
              {page}
            </button>
          ))}
          <button
            onClick={goNext}
            disabled={!canGo.next}
            className="m-1 px-2 py-1 border rounded-md"
          >
            <ChevronRightIcon className="h-6 w-4 text-blue-500" />
          </button>
          <select
            className="px-2 py-[6px] border rounded-md w-30 bg-white"
            value={pageSize}
            onChange={(e) => setPerPage(e.target.value)}
          >
            {[10, 50, 100].map((pageSize) => (
              <option className="py-2" value={pageSize} key={pageSize}>
                {pageSize} / page
              </option>
            ))}
          </select>
        </div>
      );
    }
    
    export default memo(Pagination);
    

    You can see the full code here:

    Javascript version:

    Edit quiet-browser-z5duq2

    Typescript version:

    Edit awesome-murdock-2heycu