Pagination 만들기 (라이브러리 X)
본문 바로가기
더 알아보기/기능

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

by 은돌1113 2022. 11. 9.

오늘은 Pagination(페이지 네이션) 기능을 만들어 보았다.

라이브러리 대신 부트스트랩으로 CSS 입혔고, 기능은 JS 사용해서 만들었다.

(라이브러리를 사용하지 않은 이유 : 블로그를 찾아보던 중 react-js-pagination이라는 라이브러리를 테스트해보았는 데

CSS가 안 먹히거나 먹혀도 기능이 동작을 안 하는 경우가 생겨서 그냥 만들어 보았다.)

 

react-js-pagination

Simple, easy to use component for pagination. Compatible with bootstrap paginator stylesheets. Latest version: 3.0.3, last published: 3 years ago. Start using react-js-pagination in your project by running `npm i react-js-pagination`. There are 108 other p

www.npmjs.com


구현 화면

 


코드 (설명은 주석으로 기재함)

// /pages/test.js

import Table from "@/components/layout/table/Table"
import Pagination from "@/elements/Pagination"
import { useState } from "react"

const Test = () => {
  const [page, setPage] = useState(1)

  // 임시 데이터
  const list = [
    {
      idx: 1,
      이름: "오떙떙",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "인스타그램",
      등록일시: "2022-11-08",
      신규등록여부: true,
    },
    {
      idx: 2,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
    {
      idx: 3,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
    {
      idx: 4,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
    {
      idx: 5,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
    {
      idx: 6,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
    {
      idx: 7,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
    {
      idx: 8,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
    {
      idx: 9,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
    {
      idx: 10,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
    {
      idx: 11,
      이름: "오댕댕",
      전화번호: "01012341234",
      문의지점: "어딘가",
      알게된경로: "페이스북",
      등록일시: "2022-11-08",
      신규등록여부: false,
    },
  ]

  return (
    <div>
      {/* 
        - sideBarType : 생략 가능
        - list (array) : 출력할 데이터 목록
        - page (number) : 현재 페이지
      */}
      <Table sideBarType="personalInquiry" list={list} page={page} />
      {/* 
        - list (array) : 출력할 데이터 목록
        - page (number) : 현재 페이지
        - setPage (function) : 현재 페이지 수정 함수
        - totalPage (number) : 페이징 수
          (페이지가 하나만 필요할 때는 1 넘겨주고, 
            하나 이상일 때는 list에서 10씩 끊어서 반올림 해주면 된다.)
      */}
      <Pagination
        page={page}
        setPage={setPage}
        totalPage={list ? Math.ceil(list.length / 10) : 1}
      />
    </div>
  )
}

export default Test

 

// /elements/Table.js

import { useEffect, useState } from "react"
import Image from "next/image"
import Router from "next/router"

import styles from "@/styles/Table.module.css"

const Table = (props) => {
  const { sideBarType, list, page, slice = 10 } = props
  // slice를 넣어주면 그에 따라 한 페이지당 목록 잘라주고, 없을 경우 10으로 설정
  
  const theads = [
    "이름",
    "전화번호",
    "문의 지점",
    "알게 된 경로",
    "등록일시",
    "신규 등록 여부",
  ]
  
  const tbodys = [
    "이름",
    "전화번호",
    "문의지점",
    "알게된경로",
    "등록일시",
    "신규등록여부",
  ]

  const [forwardPage, setForwardPage] = useState(1)
  const [finalPage, setFinalPage] = useState(slice)

  const onClickHandler = () => 
  	// 상세 페이지 이동 기능...
  }

  useEffect(() => {
    /*
      ex, 1 페이지에서는 idx가 1 ~ 10까지 출력
          2 페이지에서는 idx가 11 ~ 20까지 출력
          3 페이지에서는 idx가 21 ~ 30까지 출력
      => 위 과정에서 1, 11, 21을 forwardPage state로, 10, 20, 30을 finalPage state로 구현
         반복되는 과정을 공식으로 생각하여
         -> forwardPage state에서는 현재 페이지가 1일 경우 1 부여, 나머지 경우에는 page * 10 - 9를 해줌
            (나머지 경우에 finalPage + 1이 아닌 이유는 >, >>일 때는 문제가 없지만 <, <<일 경우 forwardPage가 finalPage 보다 커지기 때문에 안된다.)
         -> finalPage state에서는 현재 페이지 * 10을 해줌

      + page가 변할 때마다 forwardPage state와 finalPage state를 다시 계산해줌
      + (slice - 1) : 10개씩 끊는 경우는 9를 빼줘야 11 ~ 20로 출력되는 데 경우에 따라 달라지기 때문에 (slice - 1)로 구현
    */
    let _forwardPage = page === 1 ? 1 : page * slice - (slice - 1)
    let _finalPage = page * slice

    setForwardPage(_forwardPage)
    setFinalPage(_finalPage)
  }, [page])

  return (
    <div>
      <table className={`table table-hover ${styles.table}`}>
        <thead>
          <tr>
            {theads.map((head) => {
              return (
                <th key={head} scope="col">
                  {head}
                </th>
              )
            })}
          </tr>
        </thead>
        <tbody className="pointer">
          {list.map((data, i) => {
            // 비교 할 때 forwardPage <= i + 1 <= finalPage 이렇게 하면 정확한 계산이 안됨 아래와 같이 해야 함
            if (forwardPage <= i + 1 && i + 1 <= finalPage) {
              return (
                <tr key={data.idx} onClick={onClickHandler}>
                  {tbodys.map((body) => {
                    return (
                      <td key={body}>
                        {data[body]}
                      </td>
                    )
                  })}
                </tr>
              )
            }
          })}
        </tbody>
      </table>
    </div>
  )
}

export default Table

 

// /elements/Pagination.js

import Head from "next/head"

const Pagination = ({ page, setPage, totalPage }) => {
  // if문 대신 mapping 관계로 함수를 빼서 사용
  const paginationMap = {
    "<<": () => {
      setPage(1)
    },
    "<": () => {
      page === 1
        ? setPage(1)
        : setPage((prev) => {
            return prev - 1
          })
    },
    ">": () => {
      page === totalPage
        ? setPage(totalPage)
        : setPage((prev) => {
            return prev + 1
          })
    },
    ">>": () => {
      setPage(totalPage)
    },
  }

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

  return (
    <> 
      <nav aria-label="Page navigation example">
        <ul className="pagination">
          <li className="page-item">
            <a
              className={`page-link pointer ${
                page === 1 ? "disabled" : undefined
              }`}
              onClick={() => {
                handlePageChange("<<")
              }}
            >
              {"<<"}
            </a>
          </li>
          <li className="page-item">
            <a
              className={`page-link pointer ${
                page === 1 ? "disabled" : undefined
              }`}
              onClick={() => {
                handlePageChange("<")
              }}
            >
              {"<"}
            </a>
          </li>
          {/* 
            Number 형태로 totalPage를 받아오기 때문에 .map()을 사용하기 위해서 Array로 만듦
            이때 고유값을 주기 위해 {idx: i + 1}을 부여함
          */}
          {[
            ...Array(totalPage)
              .fill()
              .map((arr, i) => {
                return { idx: i + 1 }
              }),
          ].map((item, i) => {
            return (
              <li
                className={`page-item pointer ${
                  item.idx === page ? "active" : undefined
                }`}
                key={item.idx}
              >
                <a
                  className="page-link"
                  onClick={() => {
                    handlePageChange(item.idx)
                  }}
                >
                  {item.idx}
                </a>
              </li>
            )
          })}
          <li className="page-item">
            <a
              className={`page-link pointer ${
                page === totalPage ? "disabled" : undefined
              }`}
              onClick={() => {
                handlePageChange(">")
              }}
            >
              {">"}
            </a>
          </li>
          <li className="page-item">
            <a
              className={`page-link pointer ${
                page === totalPage ? "disabled" : undefined
              }`}
              onClick={() => {
                handlePageChange(">>")
              }}
            >
              {">>"}
            </a>
          </li>
        </ul>
      </nav>
    </>
  )
}

export default Pagination

 

결론적으로는 팀장님이 백엔드에서 작업해주셔서 위 기능을 사용하지는 않았지만 한번 시도해봤다는 것에 의의를 두었다!

 

+ 2023.02.14
: 백엔드에서 넘겨주는 totalPage를 가지고 페이지네이션 구현 (페이지네이션은 기본적으로 4개씩 끊어서 사용)

import { useContext, useState } from "react"
import CommonContext from "@/components/CommonContext"

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

  const [currentPage, setCurrentPage] = useState(1)
  let firstNum = currentPage - (currentPage % 4) + 1
  let lastNum = currentPage - (currentPage % 4) + 4

  const _totalPage = [
    ...Array(totalPage)
      .fill()
      .map((arr, i) => {
        return { idx: i + 1 }
      }),
  ]

  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 - 4 - ((totalPage - 4) % 4) + 4

      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 (
    <>
      <nav aria-label="Page navigation example">
        <ul className="pagination">
          <li className="page-item">
            <a
              className={`page-link pointer ${page === 1 ? "disabled" : undefined}`}
              onClick={() => {
                handlePageChange("<<")
              }}
            >
              {"<<"}
            </a>
          </li>
          <li className="page-item">
            <a
              className={`page-link pointer ${page === 1 ? "disabled" : undefined}`}
              onClick={() => {
                handlePageChange("<")
              }}
            >
              {"<"}
            </a>
          </li>
          {/* 
            Number 형태로 totalPage를 받아오기 때문에 .map()을 사용하기 위해서 Array로 만듦
            이때 고유값을 주기 위해 {idx: i + 1}을 부여함
          */}
          {_totalPage.map((item, i) => {
            if (item.idx >= firstNum && item.idx <= lastNum) {
              return (
                <li
                  key={item.idx}
                  className={`page-item page-item-number pointer ${
                    item.idx === page ? "active" : undefined
                  }`}
                >
                  <a
                    className={`page-link ${brandType}PaginationBg`}
                    onClick={() => {
                      handlePageChange(item.idx)
                    }}
                  >
                    {item.idx}
                  </a>
                </li>
              )
            }
          })}
          <li className="page-item">
            <a
              className={`page-link pointer ${page === totalPage ? "disabled" : undefined}`}
              onClick={() => {
                handlePageChange(">")
              }}
            >
              {">"}
            </a>
          </li>
          <li className="page-item">
            <a
              className={`page-link pointer ${page === totalPage ? "disabled" : undefined}`}
              onClick={() => {
                handlePageChange(">>")
              }}
            >
              {">>"}
            </a>
          </li>
        </ul>
      </nav>
    </>
  )
}

export default Pagination

댓글