[Next.js] Grouping Pagination 만들기 (라이브러리 X)
본문 바로가기
더 알아보기/기능

[Next.js] Grouping Pagination 만들기 (라이브러리 X)

by 은돌1113 2023. 12. 11.

이전에 구현했던 페이지네이션에서...(그룹화) 기능을 추가한 버전으로 업그레이드하게 되면서 해당 내용을 정리해 보았습니다.

 

 

Pagination 만들기 (라이브러리 X)

오늘은 Pagination(페이지 네이션) 기능을 만들어 보았다. 라이브러리 대신 부트스트랩으로 CSS 입혔고, 기능은 JS 사용해서 만들었다. (라이브러리를 사용하지 않은 이유 : 블로그를 찾아보던 중 reac

eundol1113.tistory.com

 

 

[React-query] Pagination에 Prefetching 사용하기

현재 진행 중인 프로젝트에 페이지네이션 기능을 구현했는 데 페이지를 넘길 때마다 데이터를 불러오는 속도보다 화면 전환 속도가 더 빨라서(데이터 불러오는 속도 < 화면 전환 속도) 페이지가

eundol1113.tistory.com

 

결과 화면  & 전체 코드

더보기
import React, { useContext, useEffect, useState } from "react"
import Image from "next/image"

import CommonContext from "@/components/CommonContext"

import { useQueryClient } from "react-query"

const Pagination = ({ queryKey, queryFn, page, setPage, totalPage }) => {
  const { brandType } = useContext(CommonContext)

  const queryClient = useQueryClient()

  const [currentPage, setCurrentPage] = useState(1)
  const [currentGroupIndex, setCurrentGroupIndex] = useState(0)
  const groupDivisionStandard = 5
  let firstNum = currentPage - (currentPage % 10) + 1
  let lastNum = currentPage - (currentPage % totalPage) + totalPage

  const _totalPage = [
    ...Array(totalPage)
      .fill()
      .map((_, i) => i + 1),
  ]

  const division = (arr, n) => {
    const length = arr.length
    const divide = Math.floor(length / n) + Boolean(Math.floor(length % n))

    const newArr = []

    for (let i = 0; i < divide; i++) {
      newArr.push(arr.splice(0, n))
    }

    return newArr
  }

  const groupArr = division([..._totalPage], groupDivisionStandard)

  // 데이터가 늦게 로딩되는 것을 방지하기 위해 react-query의 prefetching을 사용하여 다음 페이지의 정보를 미리 불러옴
  // 참고 : https://velog.io/@tkdals0978/React-Query-Pagination%EA%B3%BC-Prefetching
  useEffect(() => {
    if (page <= totalPage - 1) {
      const nextPage = page + 1
      queryClient.prefetchQuery([queryKey, nextPage], queryFn)
    }
  }, [page, queryClient])

  useEffect(() => {
    const groupIndex = groupArr.findIndex((item) => item.includes(page))

    setCurrentGroupIndex(groupIndex)
  }, [page])

  const paginationMap = {
    "<<": () => {
      setPage(1)
      setCurrentPage(1)
    },
    "<": () => {
      page === 1
        ? setPage(1)
        : setPage((prev) => {
            return prev - 1
          })
      setCurrentPage(page - 2)
    },
    ">": () => {
      page === totalPage
        ? setPage(totalPage)
        : setPage((prev) => {
            return prev + 1
          })
      setCurrentPage(page)
    },
    ">>": () => {
      const pageNum = totalPage - 10 - ((totalPage - 10) % 10) + 10

      if (pageNum === totalPage) {
        setPage(totalPage)
        setCurrentPage(totalPage - 1)
      } else {
        setPage(totalPage)
        setCurrentPage(totalPage)
      }
    },
  }

  const handlePageChange = (_page) => {
    if (typeof _page === "string") {
      paginationMap[_page]()
    } else {
      setPage(_page)
    }
  }

  return (
    <PaginationCss>
      <a
        className={`${page === 1 ? "disabled" : undefined}`}
        onClick={() => {
          handlePageChange("<<")
        }}
      >
        <div className="page-item">
          <Image src="/img/pagination/chevronsRight.png" width={11} height={10} alt="chevronsRight" />
        </div>
      </a>
      <a
        className={`${page === 1 ? "disabled" : undefined}`}
        onClick={() => {
          handlePageChange("<")
        }}
      >
        <div className="page-item rightGap">
          <Image src="/img/pagination/chevronRight.png" width={6} height={12} alt="chevronsRight" />
        </div>
      </a>
      {_totalPage.map((item) => {
        // 첫 페이지, 총 페이지 내에서만 li가 생성되도록 설정
        if (item >= firstNum && item <= lastNum) {
          if (groupArr[currentGroupIndex].includes(item)) {
            const isStartOfGroup = currentGroupIndex > 0 && item === groupArr[currentGroupIndex][0]
            const isEndOfGroup =
              currentGroupIndex < groupArr.length &&
              groupArr[currentGroupIndex + 1] &&
              item === groupArr[currentGroupIndex][groupDivisionStandard - 1]

            return (
              <React.Fragment key={item}>
                {isStartOfGroup && (
                  <div className="group">
                    <a
                      className={`page-link ${brandType}PaginationBg`}
                      onClick={() => {
                        handlePageChange(1)
                      }}
                    >
                      <div className={`page-item page-item-number pointer ${item === 1 ? "active" : undefined}`}>1</div>
                    </a>
                    <Image src="/img/pagination/pagination.png" width={24} height={24} alt="paginationIcon" />
                  </div>
                )}
                <a
                  className={`page-link ${brandType}PaginationBg`}
                  onClick={() => {
                    handlePageChange(item)
                  }}
                >
                  <div className={`page-item page-item-number pointer ${item === page ? "active" : undefined}`}>
                    {item}
                  </div>
                </a>
                {isEndOfGroup && (
                  <div className="group">
                    <Image src="/img/pagination/pagination.png" width={24} height={24} alt="paginationIcon" />
                    <a
                      className={`page-link ${brandType}PaginationBg`}
                      onClick={() => {
                        handlePageChange(totalPage)
                      }}
                    >
                      <div
                        className={`page-item page-item-number pointer ${item === totalPage ? "active" : undefined}`}
                      >
                        {totalPage}
                      </div>
                    </a>
                  </div>
                )}
              </React.Fragment>
            )
          }
        }
      })}
      <a
        className={`page-link pointer ${page === totalPage || totalPage === 1 ? "disabled" : undefined}`}
        onClick={() => {
          handlePageChange(">")
        }}
      >
        <div className="page-item leftGap">
          <Image
            className="chevronLeft"
            src="/img/pagination/chevronRight.png"
            width={6}
            height={12}
            alt="chevronLeft"
          />
        </div>
      </a>
      <a
        className={`page-link pointer ${page === totalPage || totalPage === 1 ? "disabled" : undefined}`}
        onClick={() => {
          handlePageChange(">>")
        }}
      >
        <div className="page-item">
          <Image
            className="chevronsLeft"
            src="/img/pagination/chevronsRight.png"
            width={11}
            height={10}
            alt="chevronsLeft"
          />
        </div>
      </a>
    </PaginationCss>
  )
}

export default Pagination

 

코드 설명

그룹화 페이지네이션을 구현할 때 중요한 부분이 "그룹화"이기 때문에, 이 부분을 페이지네이션에 적용하기 위해

 

기능 부분에서는 아래와 같이 구현하였고,

  • currentGroupIndex : 현재 그룹 위치
  • groupDivisionStandard : 페이지네이션 그룹화 기준 개수
  • division() : 그룹화할 배열, 그룹화할 기준 개수를 인자로 받아 이중배열로 그룹화된 배열을 반환하는 함수
  • groupArr : division()에서 반환된 이중배열을 담는 변수
  • useEffect(() => {... }, page) : groupArr 내에 있는 각 배열에서 현재 페이지(page)를 담고 있는 배열의 index를 반환하여 currentGroupIndex에 값을 넣어주는 함수를  현재 페이지(page)가 변할 때마다 실행


JSX에서는 아래와 같이 추가적으로 조건에 따라... 이 보이도록 구현하였다.

  • groupArr [currentGroupIndex]. includes(item) : 현재 그룹에 item이 있으면 true, 없으면 false + true인 경우에만 아래 코드 수행
  • isStartOfGroup : currentGroupIndex(현재 그룹)이 0보다 크면서, currentGroupIndex(현재 그룹)에 0번째 값이 item과 같을 경우 true, 다르면 false

    (아래와 같이 보임)

  • isEndOfGroup :
    • currentGroupIndex < groupIndex.length : currentGroup이 groupArr.length(그룹 배열 길이) 보다 작으면서
    • groupArr [currentGroupIndex + 1] : 다음 그룹이 있으면서
    • item === groupArr [currentGroupIndex][groupDivisionStandard - 1] : item이 현재 그룹(groupArr [currentGroupIndex]에 마지막 index 값([groupDivisionStandard  - 1])과 같으면
    • 위 조건을 모두 충족하면 true, 다르면 false

      (아래와 같이 보임)

댓글