[React]Pagination 구현하기

Hyoyoung Kim·2023년 4월 4일
0
post-thumbnail

★😎 쇼핑몰 제작 프로젝트를 진행하면서 pagination을 만들어야 했었다.

🧐 pagination이란?

페이지네이션이란 여러개의 컨텐츠를 여러 페이지로 나누고 페이지 번호 버튼, 이전 버튼, 다음 버튼을 눌러서 페이지를 이동하는 기능이다.

pagination 준비 조건

페이지 네이션에는 총 페이지 개수, 화면에 보여질 페이지 그룹, 화면에 보여질 페이지의 첫번쨰 페이지 번호, 화면에 보여질 페이지의 마지막 페이지 번호의 값이 필요하다.

백엔드에 size(한 페이지당 받는 데이터 수), page(페이지 숫자)를 보내서 받은 데이터를 가지고 pagination을 구현해야 한다.

😢처음에 하는 방법을 몰라 전체 데이터를 가지고 pagination을 만들어 버렸다. 이렇게 전체 데이터를 가지고 pagination을 만들다보면 서버가 터져버리는 이슈가 발생할 수 있다. 그걸 모르고 만들다가 아주 고생을 해버렸다. 두번 일하게 되어버린..

아래 코드는 size와 page를 axios요청을 보내서 서버에서 데이터를 받는 로직이다.

 //받은 데이터들
  const [memberList, setMemberList] = useState([]);
// 작성날짜 순으로 데이터들 나열해서 받는 sort
  const [sort] = useState(`sort=createDate,desc`);
  // 페이지 숫자
  const [page, setPage] = useState(0);
  // 전체 데이터 리스트 개수
  const [totalPages, setTotalPages] = useState(0);
//한 페이지당 받는 데이터 수
  const [size] = useState(7);
  //검색리스트 길이
  const [listLength, setListLength] = useState(0);

  useEffect(() => {
    const getData = async () => {
      await axios({
        method: 'get',
        url: `${process.env.REACT_APP_API_URL}/itemQna/list?
size=${size}&page=${page}&${sort}`,
      }).then((res) => {
        setMemberList(res.data.content);
        setTotalPages(res.data.totalElements);
        setListLength(res.data.numberOfElements);
      });
    };
    getData();
  }, [sort, size, page]);

이렇게 받은 데이터를 브라우저에 띄어야한다.

// 여기서 data는 memberList이다. 
          data.map((el: any, index: any) => {
            return (
              <S.Container key={index} onClick={() => moveQnApw(el.id)}>
                <div>{el.id}</div>
                <div>
                  <ul>{el.title}</ul>
                  <TfiLock color='#D9D9D9' />
                </div>
                {isDesktopOrMobile !== true ? (
                  <div>
                    <p>{el.memberId}</p>
                    <p>{registDate2(el.createDate)}</p>
                  </div>
                ) : (
                  <div>
                    <ul>{el.memberId}</ul>
                    <ul>{registDate2(el.createDate)}</ul>
                  </div>
                )}
              </S.Container>
            );
          })

이런 코드를 작성하면 화면에 서버에서 받아온 데이터들이 브라우저 상에 나온다. 그렇다면 이제 pagination을 만들어야한다.

나는 pagination을 컴포넌트화해서 따로 빼주었다.

//현재페이지
  const [currentPage, setCurrentPage] = useState(props.page + 1);
  //페이지당 보여지는 리스트
  const [itemsPerPage] = useState(props.size);

  //브라우저상 보여지는 한계 숫자
  const [pageNumberLimit] = useState(5);
  const [maxPageNumberLimit, setMaxPageNumberLimit] = useState(5);
  const [minPageNumberLimit, setMinPageNumberLimit] = useState(0);
<Pagination

// 여기서 data는 memberList이다. 
        data={props.data}
        totalPages={props.totalPages}
        page={props.page}
        setPage={props.setPage}
        currentPage={currentPage}
        setCurrentPage={setCurrentPage}
        itemsPerPage={itemsPerPage}
        pageNumberLimit={pageNumberLimit}
        maxPageNumberLimit={maxPageNumberLimit}
        setMaxPageNumberLimit={setMaxPageNumberLimit}
        minPageNumberLimit={minPageNumberLimit}
        setMinPageNumberLimit={setMinPageNumberLimit}
      />

pagination 컴포넌드

1. 먼저 전체 컨텐츠 개수를 한 페이지에 보여주고자 하는 컨텐츠의 개수로 나눠주고 Math.ceil로 올림해주어 계산하였습니다.

for문을 돌려 1부터 전체 페이지까지 pages 배열에 담아주었다.

  const pages = [];
  for (let i = 1; i <= Math.ceil(props.totalPages / props.itemsPerPage); i++) {
    pages.push(i);
  }

2. 페이지네이션 숫자를 눌렀을 시

pages 배열을 map으로 순회해 해당 number 값이 브라우저 상에 보여지는 최대 숫자(5) 이하이거나 최소숫자(0) 초과일때 브라우저 상에 해당 number값을 띄우도록 로직을 구현하였다. 해당 number을 눌렀을 시 그 값이 useState로 값을 설정한 현재 페이지(currentPage)에 넣어주도록 설정하였다.

  const handleClick = (event: any) => {
    props.setCurrentPage(Number(event.target.id));
    props.setPage(event.target.id - 1);
  };

  const renderPageNumbers = pages.map((number: any) => {
    if (number < props.maxPageNumberLimit + 1 && number > props.minPageNumberLimit) {
      return (
        <S.Paging2
          key={number}
          id={number}
          onClick={handleClick}
          style={number === props.currentPage ? totalColor : {}}
        >
          {number}
        </S.Paging2>
      );
    } else {
      return null;
    }
  });

3. 다음 버튼을 눌렀을 시

다음버튼을 눌렀을 시 현재 페이지에서 1을 추가해준다.
만약 현재페이지에서 1 더한 값이 브라우저상에서 보여지는 최대 숫자값(maxPageNumberLimit)보다 클 경우 브라우저 상에 보여지는 최대 숫자값과 최소 숫자값에 각각 5를 더해 늘려준다.

  const handleNextbtn = () => {
    props.setCurrentPage(props.currentPage + 1);
    props.setPage(props.page + 1);

    if (props.currentPage + 1 > props.maxPageNumberLimit) {
      props.setMaxPageNumberLimit(props.maxPageNumberLimit + props.pageNumberLimit); // 5+5 =10
      props.setMinPageNumberLimit(props.minPageNumberLimit + props.pageNumberLimit); //0+5 = 5
    }
  };

다음버튼을 눌렀을 시 현재 페이지가 pages배열의 0번재 인덱스랑 같다면 disabled 처리해준다. 즉, 처음 페이지라면 클릭 못하도록 설정

disabled={props.currentPage === pages[0] ? true : false}

4. 이전 버튼을 눌렀을 시

이전버튼을 눌렀을 시 현재 페이지에서 1을 빼준다.
만약 현재페이지에서 1을 빼준 값을 5로 나눴을 때 나머지가 0이라면 브라우저 상에 보여지는 최대 숫자값과 최소 숫자값에 각각 5를 빼준다.

  const handlePrevbtn = () => {
    props.setCurrentPage(props.currentPage - 1);
    props.setPage(props.page - 1);

    if ((props.currentPage - 1) % props.pageNumberLimit === 0) {
      props.setMaxPageNumberLimit(props.maxPageNumberLimit - props.pageNumberLimit);//10 -5 =5
      props.setMinPageNumberLimit(props.minPageNumberLimit - props.pageNumberLimit); // 5-5 =0
    }
  };

이전 버튼을 눌렀을 시 현재 페이지가 pages 배열의 마지막 인덱스와 같다면 disabled 처리 해준다. 즉, 마지막 페이지라면 클릭 안되도록 설정

disabled={props.currentPage === pages[pages.length - 1] ? true : false}

전체 코드

import { AiOutlineLeft, AiOutlineRight } from 'react-icons/ai';
import * as S from './style';
import React from 'react';

export const Pagination = (props: any) => {


  const numberColor = '#289951';
  const numbertxtColor = 'white';
  const totalColor = {
    backgroundColor: numberColor,
    color: numbertxtColor,
  };

  const pages = [];
  for (let i = 1; i <= Math.ceil(props.totalPages / props.itemsPerPage); i++) {
    pages.push(i);
  }

  const handleClick = (event: any) => {
    props.setCurrentPage(Number(event.target.id));
    props.setPage(event.target.id - 1);
  };

  const renderPageNumbers = pages.map((number: any) => {
    if (number < props.maxPageNumberLimit + 1 && number > props.minPageNumberLimit) {
      return (
        <S.Paging2
          key={number}
          id={number}
          onClick={handleClick}
          style={number === props.currentPage ? totalColor : {}}
        >
          {number}
        </S.Paging2>
      );
    } else {
      return null;
    }
  });

  const handleNextbtn = () => {
    props.setCurrentPage(props.currentPage + 1);
    props.setPage(props.page + 1);

    if (props.currentPage + 1 > props.maxPageNumberLimit) {
      props.setMaxPageNumberLimit(props.maxPageNumberLimit + props.pageNumberLimit);
      props.setMinPageNumberLimit(props.minPageNumberLimit + props.pageNumberLimit);
    }
  };

  const handlePrevbtn = () => {
    props.setCurrentPage(props.currentPage - 1);
    props.setPage(props.page - 1);

    if ((props.currentPage - 1) % props.pageNumberLimit === 0) {
      props.setMaxPageNumberLimit(props.maxPageNumberLimit - props.pageNumberLimit);
      props.setMinPageNumberLimit(props.minPageNumberLimit - props.pageNumberLimit);
    }
  };

  return (
    <S.PageNumbers>
      <S.LiPageNumbers>
        <S.ButtonPageNumbers
          onClick={handlePrevbtn}
          disabled={props.currentPage === pages[0] ? true : false}
        >
         <AiOutlineLeft size='30' /> 
        </S.ButtonPageNumbers>
      </S.LiPageNumbers>
      {renderPageNumbers}
      <S.LiPageNumbers>
        <S.ButtonPageNumbers
          onClick={handleNextbtn}
          disabled={props.currentPage === pages[pages.length - 1] ? true : false}
        >
       <AiOutlineRight size='30' /> 
        </S.ButtonPageNumbers>
      </S.LiPageNumbers>
    </S.PageNumbers>
  );
};

pagination styled-component

import styled from 'styled-components';

export const PageNumbers = styled.ul`
  list-style: none;
  display: flex;
  align-items: center;
  margin-bottom: 1.2rem;
  justify-content: center;
  @media screen and (max-width: 720px) {
    margin-bottom: 0;
  }
`;

export const LiPageNumbers = styled.li`
  padding: 0.1rem;
  cursor: pointer;
  &:active {
    color: black;
  }
`;

export const ButtonPageNumbers = styled.button`
  background-color: transparent;
  border: none;
  font-size: 0.17rem;
  margin-bottom: 0.6rem;
  cursor: pointer;
  &:hover {
    color: black;
  }
  &:focus {
    outline: none;
  }
  @media screen and (max-width: 720px) {
    margin-bottom: 0;
    font-size: 0.5rem;
  }
`;

export const Paging2 = styled.li`
  padding: 0.1rem;
  cursor: pointer;
  font-size: 0.17rem;
  color: ${({ theme }) => theme.palette.txtgray};
  &:active {
    background-color: ${({ theme }) => theme.palette.green};
    cursor: pointer;
    color: white;
  }
  @media screen and (max-width: 720px) {
    font-size: 0.5rem;
    padding: 5% 10%;
  }
`;

0개의 댓글