Pagination 컴포넌트 구현하기

Molly·2023년 5월 23일
2

React, Next.js

목록 보기
2/7
post-thumbnail

페이지네이션은 왜 필요할까?


웹 개발을 하다보면, 게시판 혹은 목록을 만들게 되는 일이 빈번하다.
게시판에 표시할 데이터의 수 자체가 적으면 문제가 안되겠지만,
많은 경우에 게시판에 올라가야할 글이 수 천, 수 만이 되는 경우가 허다하다.

하지만 이런 대량의 데이터를 한 번에 전부 가져오는 일은 옳지 못하는 경우게 많다.
실제로, MySql에 저장된 10개의 열을 가진 2000개의 데이터를 단순히 fetching하는 것만으로
도 화면이 조금 끊기는게 보이며, 이는 UX에 좋지 않기 때문이다.

Pagination은 이런 문제에 대한 적합한 해결책이다. 보통 20~100개의 데이터만을 fetching하여 첫 페이지를 보여주고, 사용자가 숫자 버튼을 클릭할 때 마다, 다음 20~100개의 데이터를 fetching해 와 게시판에 표시하는 방식을 택한다.

SWR로 데이터 가져오기


MySql에 저장되어 있는 자산 데이터들을 가져오기 위해서, useSWR을 활용했습니다.

const { data, error, isValidating, mutate } = useSWR(
  `/api/allassets?&page=${state.page}&limit=${state.limit}...`,
  fetcher
);

기본적으로 Context API를 통해 정의된 전역 state가 query parameter로 전달되고,
dispatch event를 통해, 변경된 파라미터가 전달되며 새로운 데이터를 가져옵니다.

즉, 페이지네이션의 현재 page와 한 번에 보여질 데이터의 갯수인 limit을 state 변수로 정의해놓고 사용자들이 해당 변수의 상태를 변경할 때 마다, 이에 해당하는 데이터를 가져옵니다.


SQL Query 작성시 고려해야할 것


사용자들이 선택할 limit (데이터가 한 페이지에 보여질 개수) 및 기타 다른 필터에 따라서, 전체 페이지의 개수가 달라지므로, 생성할 총 page의 개수를 자동으로 반환하는 sql query를 데이터를 가져오면서, 함께 포함시킬 수 있으면 pagination 컴포넌트를 만들기 매우 용의하다.

사용자가 선택한 필터에 따라 가져올 총 row의 개수를 반환하는 query를 포함시켰다.


import executeQuery from "datas/mysql";

const handler=async(req,res)=>{
    const {page,limit, ...}=req.query

    try{
        const rowNum=await executeQuery({
            query: `SELECT count(*) FROM RMS.ALL_ASSETS 
            WHERE 1=1
			... 
			//req.query로 넘겨받은 상태 변수들을 where 절에 포함
			`;	
		
        })
        const parsedRow=await JSON.parse(JSON.stringify(rowNum))
        const rowCount=parsedRow[0]['count(*)']
        const result=await executeQuery({
            query: `SELECT al.*,cc.*
            FROM RMS.ALL_ASSETS al 
            LEFT JOIN RMS.CHART_TMP cc 
            ON cc.ITEM_CD = al.ITEM_CD AND cc.CHART_TP=7 
            WHERE 1=1
			... 
			//req.query로 넘겨받은 상태 변수들을 where 절에 포함

            LIMIT ${limit*(page-1)}, ${limit}
            ;`
        })

        res.status(200).json([{assets:[...result]},{rowCount:rowCount}])
    } catch(err){
        res.status(401).json({ message: "Can't find data" });
    }
}

export default handler;

이 코드에서 LIMIT ${limit*(page-1)}, ${limit} 이 pagination의 핵심이라고 말할 수 있다. page와 limit이 변경될 때 마다, 자동으로 그에 맞는 데이터를 가져온다.


Pagination 컴포넌트


Pagination 컴포넌트에 전달한 props는 다음과 같다.

total : 총 데이터의 갯수
page : page 상태 변수
setPage : page를 변경하는 dispatch 핸들러
views : 한 페이지에 보여질 데이터 개수 (limit)


const Pagination = ({ total, page, setPage, views }: Props) => {
  const listLimit = views || 10;
  const pageLimit = 9;
  const startPage =
    parseInt((page - 1) / (pageLimit + 1) + "") * (pageLimit + 1) + 1;
  const numPages = Math.ceil(total / listLimit);
  const endPage =
    startPage + pageLimit > numPages ? numPages : startPage + pageLimit;
  const pageArray = [];
  for (let i = startPage; i <= endPage; i++) {
    pageArray.push(i);
  }
  const handlePrev = () => page !== 1 && setPage(page - 1);
  const handleNext = () => page !== numPages && setPage(page + 1);

  return (
    <main className="w-full flex justify-center">
      <div className="flex items-center min-w-[350px] mt-10 mx-auto">
        <Image
          src={arrow}
          alt=""
          onClick={handlePrev}
          className={`cursor-pointer rotate-180 mr-5 ${
            page === 1 && "opacity-30 cursor-default"
          }`}
        />
        <div className="flex justify-around mx-auto gap-2">
          {pageArray.map((i) => (
            <div
              key={i}
              onClick={() => setPage(i)}
              className={`flex justify-center items-center w-10 h-10 rounded-20 text-gray-600 text-sm bg-white border cursor-pointer ${
                page === i && "bg-[#E6F5FF] border-[#0198FF] text-[#0198FF]"
              }`}
            >
              <p className="h-4">{i}</p>
            </div>
          ))}
        </div>
        <Image
          src={arrow}
          alt=""
          onClick={handleNext}
          className={`cursor-pointer ml-5 ${
            page === numPages && "opacity-30 cursor-default"
          }`}
        />
      </div>
    </main>
  );
};

export default Pagination;

완성된 paginatino 컴포넌트는 페이지내에서 활용된다.


//... 윗 부분 생략

              {assetList.map((asset, i) => (
                <Items
                  exchg={asset['HR_ITEM_NM']}
                  krName={asset['ITEM_KR_NM']}
                  riskDescriptionKr={asset['LV_DSCP_KR']}
                  riskDescriptionEn={asset['LV_DSCP_ENG']}
                  curr={state.currency}
                  cat={asset['CAT']}
                  ...
                  />
              ))}
            </tbody>
          </table>
          <Pagination 
            total={rowCount} 
            page={state.page} 
            setPage={(page) => dispatch({ type: 'SET_PAGE', payload: page })} 
            views={state.limit} />
        </div>

마무리


Paginatnion 컴포넌트는 대량의 데이터 중 일부를 가져와 화면에 표시하고, 사용자들에게 순차적으로 보여주고자 할 때 적합한 컴포넌트이다. Infinite Loading과 함께 꼭 알아두고 제대로 활용할 수 있도록 공부해두는게 좋다고 생각한다.

profile
Next.js, Typescript, Rust

0개의 댓글