В этом посте мы рассмотрим вариант создания пагинации для таблицы, которую мы создавали в прошлом посте. Также добавим переключатель количества записей на странице (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;
Весь код вы может скачать по ссылке: