Рубріки: Решения

Пагинация в React: полезный кастомный хук

Богдан Мирченко

Фулстек-разработчик Дамилола Йоруба опубликовал в блоге туториал по разбивке страниц с помощью React-хука. Он призван помочь разобраться в концепции пагинации и в том, как ее реализовать на React. Изученный метод может быть применен к любому проекту на JavaScript. 

В чем суть

Пагинация — это процесс разделения печатного или цифрового контента на отдельные страницы. Для печатных документов и онлайн-контента пагинация также относится к автоматизированному процессу добавления последовательных номеров для определения последовательного порядка страниц. 

Например, на странице шесть элементов, а нужно отобразить только три. Это значит, что будет две страницы, а если нужно отображать по два элемента на странице, то всего будет три страницы. 

Формула проста: 

totalPages = totalContent / contentPerPage

Метод .slice() в JavaScript

После вычисления количества элементов нужно определить, какой контент отображать на конкретной странице. Для этого необходимо понять, как взаимодействуют страницы и индекс содержимого, поэтому сначала нужно разобраться с методом .slice(). 

Метод slice() копирует заданную часть массива и возвращает эту скопированную часть в виде нового массива. Исходный массив при этом не изменяется. 

Например, есть массив под названием scouts, и нужно выбрать только часть этого массива, опираясь на индекс массива. 

const scouts = ["levi", "hange", "erwin", "petra", "oruo", "miche"]
scouts.slice(2, 5)
// output: [ 'erwin', 'petra', 'oruo' ]
scouts.slice(1, 3)
// output: [ 'hange', 'erwin' ]

Массивы в JavaScript индексируются с нуля, поэтому первый параметр — это индекс, с которого нужно начать срез, а второй параметр — это индекс сразу после того места, где нужно, чтобы срез закончился. Например, если нужен срез от 2 до 4, используется .slice(2, 5), как показано на примере выше.

Сопоставление номера страницы с индексом

Все, что нужно сделать, это узнать, какими должны быть startIndex и lastIndex на основе номера страницы. 

Пример выше показывает, что lastIndex —это просто текущая страница, умноженная на заданное содержание страницы, а startIndex — это содержание страницы, вычтенное из lastIndex

// assuming we are on page one
const page = 1;
const contentPerPage = 3
const lastIndex = page * contentPerPage // 3
const firstIndex = lastIndex - contentPerPage // 0

scouts.slice(firstIndex, lastIndex)
// scouts.slice(0, 3) => [ 'levi', 'hange', 'erwin' ]

// page 2
// scouts.slice(3, 6) => [ 'petra', 'oruo', 'miche' ]

Кастомный хук usePagination

Этот хук принимает объект, который принимает свойства contentPerPage — сколько элементов должно отображаться за один раз и count — общее количество заданных элементов (длина массива). Он также возвращает объект со следующими свойствами: 

  • page — текущая страница, на которой находится пользователь;
  • totalPages — общее количество сгенерированных страниц;
  • firstContentIndex — первый индекс для метода .slice();
  • lastContentIndex — последний индекс для метода .slice();
  • nextPage — функция для перехода на одну страницу вперед;
  • prevPage — функция для перехода на одну страницу назад;
  • setPage — функция для перехода на определенную страницу.

Значения типов: 

interface UsePaginationProps {
    contentPerPage: number,
    count: number,
}

interface UsePaginationReturn {
    page: number;
    totalPages: number;
    firstContentIndex: number;
    lastContentIndex: number;
    nextPage: () => void;
    prevPage: () => void;
    setPage: (page: number) => void;
}

type UsePagination = (UsePaginationProps) => (UsePaginationReturn);

Далее необходимо создать в проекте React папку hooks и файл usePagination, в котором будет находиться хук. 

Напишите в нем следующее: 

import { useState } from "react";

const usePagination: UsePagination = ({ contentPerPage, count }) => {
  const [page, setPage] = useState(1);
  // number of pages in total (total items / content on each page)
  const pageCount = Math.ceil(count / contentPerPage);
  // index of last item of current page
  const lastContentIndex = page * contentPerPage;
  // index of first item of current page
  const firstContentIndex = lastContentIndex - contentPerPage;

  // change page based on direction either front or back
  const changePage = (direction: boolean) => {
    setPage((state) => {
      // move forward
      if (direction) {
        // if page is the last page, do nothing
        if (state === pageCount) {
          return state;
        }
        return state + 1;
        // go back
      } else {
        // if page is the first page, do nothing
        if (state === 1) {
          return state;
        }
        return state - 1;
      }
    });
  };

  const setPageSAFE = (num: number) => {
    // if number is greater than number of pages, set to last page
    if (num > pageCount) {
      setPage(pageCount);
      // if number is less than 1, set page to first page
    } else if (num < 1) {
      setPage(1);
    } else {
      setPage(num);
    }
  };

  return {
    totalPages: pageCount,
    nextPage: () => changePage(true),
    prevPage: () => changePage(false),
    setPage: setPageSAFE,
    firstContentIndex,
    lastContentIndex,
    page,
  };
};

export default usePagination;

Значение текущей страницы управляется с помощью useState. Следует обратить внимание, что pageCount также равен значению последней страницы. 

Реализация хука

Для реализации хука его нужно импортировать, а затем ввести необходимые свойства. 

...
  const {
    firstContentIndex,
    lastContentIndex,
    nextPage,
    prevPage,
    page,
    setPage,
    totalPages,
  } = usePagination({
    contentPerPage: 3,
    count: people.length,
  });
...

Затем данные просто срезаются с помощью firstContentIndex и lastContentIndex

...
<div className="items">
  {people
    .slice(firstContentIndex, lastContentIndex)
    .map((el: any) => (
      <div className="item" key={el.uid}></div>
   ))}
</div>
...

Код ниже позволяет создать кнопки, а также добавить соответствующие дескрипторы onClick

<div className="pagination">
  <p className="text">
    {page}/{totalPages}
  </p>
  <button onClick={prevPage} className="page">
    &larr;
  </button>
  {/* @ts-ignore */}
  {[...Array(totalPages).keys()].map((el) => (
    <button
      onClick={() => setPage(el + 1)}
      key={el}
      className={`page ${page === el + 1 ? "active" : ""}`}
    >
      {el + 1}
    </button>
  ))}
  <button onClick={nextPage} className="page">
    &rarr;
  </button>
</div>

Вот и все. Вот что должно получиться. 

Исходный код проекта доступен по ссылке.

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023