В этом посте мы рассмотрим вариант создания пагинации для таблицы, которую мы создавали в прошлом посте. Также добавим переключатель количества записей на странице (pageSize).
Скачать готовую таблицу с прошлого примера вы можете тут. Рабочий пример с пагинацией доступен на CodeSandBox.
В папке components давайте создадим еще одну папку и назовем ее TablePagination. Там будет четыре файла:

В файле TablePagination.jsx добавим следующий код
import React, { useEffect, useState } from 'react';
import { Form, Pagination } from "react-bootstrap";
import './TablePagination.style.scss';
import usePagination, { DOTS } from "./usePagination";
const TablePagination = ({
totalCount,
changeItemsPerPage,
pageSize,
onPageChange,
currentPage,
siblingsCount = 1
}) => {
const [totalPages, setTotalPages] = useState(Math.ceil(totalCount / pageSize));
useEffect(() => {
setTotalPages(Math.ceil(totalCount / pageSize));
}, [pageSize]);
const paginationRange = usePagination({ currentPage, totalCount, siblingsCount, pageSize })
const handlePrevClick = () => {
let changedPage = currentPage > 1 ? currentPage - 1 : currentPage
onPageChange(changedPage);
}
const handleNextClick = () => {
onPageChange(currentPage + 1);
}
const handleChangePageSize = (e) => {
changeItemsPerPage(+e.target.value);
}
return (
<div className='custom-table__pagination'>
<Pagination>
<Pagination.First onClick={() => onPageChange(1)} disabled={(totalCount <= pageSize)}/>
<Pagination.Prev onClick={() => handlePrevClick()} disabled={currentPage === 1}/>
{paginationRange.map((pageNumber, index) => {
let current = index + 1;
if (pageNumber === DOTS) {
return <Pagination.Item key={index}>…</Pagination.Item>;
}
return <Pagination.Item
key={index}
active={currentPage === current}
onClick={() => onPageChange(current)}
disabled={(totalCount <= pageSize)}
>
{current}
</Pagination.Item>
})}
<Pagination.Next onClick={() => handleNextClick()} disabled={currentPage === totalPages}/>
<Pagination.Last onClick={() => onPageChange(totalPages)} disabled={(totalCount <= pageSize)}/>
</Pagination>
<Form.Select
className='select-perpage'
defaultValue={pageSize}
onChange={(e) => handleChangePageSize(e)}
>
<option value="1">1</option>
<option value="5">5</option>
<option value="10">10</option>
<option value="20">20</option>
</Form.Select>
</div>
);
};
export default TablePagination;
Наш компонент в качестве пропсов получает:
totalCount— общее количество записейchangeItemsPerPage— хендлер для обработки изменения размера страницы (pageSize)pageSize— количество отображаемых записейonPageChange— хендлер для обработки изменения страницыcurrentPage— текущая страницаsiblingsCount = 1— параметр, который устанавливает сколько отображать по соседству с текущей страницей других страниц (по умолчанию будет равен 1)
Далее мы определяем стейт totalPages — общее количество страниц. Его высчитываем в useEffect при изменении параметра pageSize. Чтобы правильно показать кол-во страниц округляем число в большую сторону с помощью Math.ceil.
Также для пагинации мы будем использовать кастомный хук — usePagination. Он будет нам возвращать массив со страницами. Вот код этого хука:
import { useMemo } from "react";
export const DOTS = '...';
const range = (start, end) => {
let length = end - start + 1;
return Array.from({ length }, (_, index) => index + start);
}
const usePagination = ({ totalCount, pageSize, siblingsCount = 3, currentPage }) => {
const paginationRange = useMemo(() => {
const totalPageCount = Math.ceil(totalCount / pageSize);
const totalPageNumbers = siblingsCount + 5;
if (totalPageNumbers >= totalPageCount) {
return range(1, totalPageCount);
}
const leftSiblingIndex = Math.max(currentPage - siblingsCount, 1);
const rightSiblingIndex = Math.min(currentPage + siblingsCount, totalPageCount);
const shouldShowLeftDots = leftSiblingIndex > 2;
const shouldShowRightDots = rightSiblingIndex < totalPageCount - 2;
const firstPageIndex = 1;
const lastPageIndex = totalPageCount;
if (!shouldShowLeftDots && shouldShowRightDots) {
let leftItemCount = 3 + 2 * siblingsCount;
let leftRange = range(1, leftItemCount);
return [...leftRange, DOTS, totalPageCount];
}
if (shouldShowLeftDots && !shouldShowRightDots) {
let rightItemCount = 3 + 2 *siblingsCount;
let rightRange = range(totalPageCount - rightItemCount + 1, totalPageCount);
return [firstPageIndex, DOTS, ...rightRange];
}
if (shouldShowLeftDots && shouldShowRightDots) {
let middleRange = range(leftSiblingIndex, rightSiblingIndex);
return [firstPageIndex, DOTS, ...middleRange, DOTS, lastPageIndex];
}
}, [totalCount, pageSize, siblingsCount, currentPage]);
return paginationRange;
}
export default usePagination;
Функция range будет возвращать нам массив в нужном диапазоне.
Также мы будем использовать хук useMemo, который будет сохранять наши результаты и перезапускать расчеты при изменении одной из зависимостей.
В зависимости от общего кол-ва страниц и от того на какой странице находимся мы, нам возможно понадобится добавлять вместо номера страницы три точки. Вот как раз эти сценарии и предусмотрены в коде (строки с 11-48).
Теперь осталось только в компоненте EditableTable вывести нашу пагинацию и добавить несколько строк кода:
const EditableTable = ({ columns, rows, actions }) => {
/*... Старый код ... */
const [pageSize, setPageSize] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
useEffect(() => {
if (currentPage >= rows.length / pageSize)
setCurrentPage(1);
}, [pageSize])
useMemo(() => {
const firstPageIndex = (currentPage - 1) * pageSize;
const lastPageIndex = firstPageIndex + pageSize;
const newData = rows.slice(firstPageIndex, lastPageIndex);
setRowsState(newData);
}, [currentPage, pageSize]);
return (
<>
{/*Оставляем вывод таблицы, я просто убрал, чтобы не загромождать код ... <Table striped bordered hover></Table>*/}
<TablePagination
totalCount={rows.length}
pageSize={pageSize}
changeItemsPerPage={page => setPageSize(page)}
onPageChange={page => setCurrentPage(page)}
currentPage={currentPage}
/>
</>
);
};
export default EditableTable;
Весь код вы может скачать по ссылке:
Добавить комментарий