Reputation: 459
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:
I need it to look like this:
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
Upvotes: 0
Views: 6196
Reputation: 3644
When doing pagination using TanStack Table, you should generally follow the official guide by utilizing pagination
state and onPaginationChange
. Using pagination
state in your React Table as your source of truth is advised — Many logic has been handled by the library, you shouldn't need a separate state canPreviousPage
and should directly utilize table.getCanPreviousPage()
instead.
Regarding to pagination range, you can use this hook written based on Mantine's usePagination. The hook read existing pagination
state directly from table
instance and computes the pagination range.
use-react-table-pagination-range.ts
import type { Table } from '@tanstack/react-table';
const range = (start: number, end: number) => {
const length = end - start + 1;
return Array.from({ length }, (_, index) => index + start);
};
export const ELLIPSIS = 'ellipsis';
export const useReactTablePaginationRange = <TData>(
table: Table<TData>,
/**
* Siblings amount on left/right side of selected page, defaults to 1.
*/
siblings = 1,
/**
* Amount of elements visible on left/right edges, defaults to 1.
*/
boundaries = 1
) => {
const total = table.getPageCount();
const activePage = table.getState().pagination.pageIndex + 1;
const totalPageNumbers = siblings * 2 + 3 + boundaries * 2;
if (totalPageNumbers >= total) {
return range(1, total);
}
const leftSiblingIndex = Math.max(activePage - siblings, boundaries);
const rightSiblingIndex = Math.min(activePage + siblings, total - boundaries);
const shouldShowLeftDots = leftSiblingIndex > boundaries + 2;
const shouldShowRightDots = rightSiblingIndex < total - (boundaries + 1);
if (!shouldShowLeftDots && shouldShowRightDots) {
const leftItemCount = siblings * 2 + boundaries + 2;
return [
...range(1, leftItemCount),
ELLIPSIS,
...range(total - (boundaries - 1), total),
] as const;
}
if (shouldShowLeftDots && !shouldShowRightDots) {
const rightItemCount = boundaries + 1 + 2 * siblings;
return [
...range(1, boundaries),
ELLIPSIS,
...range(total - rightItemCount, total),
] as const;
}
return [
...range(1, boundaries),
ELLIPSIS,
...range(leftSiblingIndex, rightSiblingIndex),
ELLIPSIS,
...range(total - boundaries + 1, total),
] as const;
};
import { useReactTable } from "@tanstack/react-table"
import { ELLIPSIS, useReactTablePaginationRange } from "@/components/hooks/use-react-table-pagination-range";
const Example = () => {
const table = useReactTable({ ... });
const paginationRange = useReactTablePaginationRange(table);
return (
<>
...
{paginationRange.map((page, index) => (
<div key={index}>
{page === ELLIPSIS ? (
<span>…</span>
) : (
<button
onClick={() => table.setPageIndex(page - 1)}
>
{page}
</button>
)}
</div>
))}
...
</>
)
}
Note that accessibility and other details are omitted for brevity.
You can take a look at this example modified directly from the official example.
Upvotes: 0
Reputation: 2675
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:
Typescript version:
Upvotes: 2