글 작성자: 망고좋아
반응형

🎯 페이지네이션 :: offset 방식

  • 페이지네이션을 구현하는 방법 중 하나인 offset 방식을 알아보자.

 

📝 페이지네이션 핵심 코드 살펴보기

{getPageNumbers(currPage, pageCount).map((page) => {
  return (
    <Button 
      key={`pagination-button-${page}`}
      active={currPage === page}
      onClick={() => onClickPage(page)}
    >
      {page+1}
    </Button>
  )
})}
  • 현재 페이지와 page가 같다면 active가 true로 되면서 배경색 설정
  • onClick 하면 현재 페이지로 상태 변경

 

function getPaginationArray(currentPage, total) {
  const resultList = [currentPage];

  let idx = 1;
  while (resultList.length < Math.min(9, total)) {
    if (currentPage - idx > -1) resultList.unshift(currentPage - idx);
    if (currentPage + idx < total) resultList.push(currentPage + idx);
    idx++;
  }

  return resultList;
}
  • 현재 페이지와 끝 페이지 숫자를 받고
  • resultList에 현재 페이지인 currentPage를 넣어준다.
  • 페이지네이션을 9개로 자를 거니까 while문을 resultPages.length < 9 만큼 돌려준다.
  • 현재 페이지가 끝페이지보다 작으면 currentPage + idx을 push
  • 현재 페이지가 -1보다 크면 currentPage - idx을 push
  • 예를 들면 현재 페이지가 첫 페이지면 currentPage는 0으로 넘어오기 때문에 if (currentPage + idx < total) 문만 동작하기 때문에 1,2,3,4,5,6,7,8,9로 배열이 들어가고 return 된다.
  • 만약 현재 페이지가 6이면 currentPage는 5로 넘어오기 때문에
  • 5
  • 4, 5 ,6
  • 3, 4, 5, 6, 7
  • 2, 3, 4, 5, 6, 7, 8
  • 1, 2, 3, 4, 5, 6, 7, 8, 9
  • 이렇게 while문이 돌아서 들어간다.

 

useEffect(() => {
  (async function () {
    if (currTab === "트랙") {
      // 페이지네이션이 바뀔 때 마다 offset이 url 바뀌도록
      const API_END_POINT = "https://api-xxxx/";
      const offset = currPage * 6;
      const trackUrl = `${API_END_POINT}track/list/?offset=${offset}&count=6`;
      const response = await axios.get(trackUrl);
      setTotalCardCount(response.data.track_count);
      setCardData(response.data.tracks);
    }

    if (currTab === "과목") {
      const API_END_POINT = "https://api-xxxx/";
      const offset = currPage * 8;
      const courseUrl = `${API_END_POINT}course/list/?offset=${offset}&count=8`;
      const response = await axios.get(courseUrl);
      setTotalCardCount(response.data.course_count);
      setCardData(response.data.courses);
    }
  })();
}, [currTab, currPage]);
  • 현재 tab과 현재 페이지가 변경될 때마다 useEffect를 실행시켜 offset을 가져온다.
  • 트랙 탭 경우 offset을 기준으로 6개씩 가져온다.

 

<Pagination
  currPage={currPage}
  pageCount={Math.ceil(totalCardCount / (currTab === "트랙" ? 6 : 8))}
  onClickPage={setCurrPage}
/>
  • pageCount 전체 페이지 경우 전체 카드 개수에서 페이지에 뿌려줄 데이터만큼 나눈 다음 올림 처리하여 값 설정.

 

📝 전체 코드

📕 App.js

import axios from "axios";
import { useState, useEffect } from "react";
import styled from "styled-components";
import Tab from "./Tab.jsx";
import SearchTextField from "./SearchTextField.jsx";
import CardCount from "./CardCount.jsx";
import TrackCard from "./TrackCard.jsx";
import CourseCard from "./CourseCard.jsx";
import Pagination from "./Pagination.jsx";

const Container = styled.div`
  display: flex;
  align-items: center;
  width: 1232px;
  margin: auto;
  padding-top: 50px;
  flex-direction: column;
`;

const TracksContainer = styled.div`
  display: grid;
  grid-template-columns: repeat(3, 398px);
  grid-column-gap: 19px;
  grid-row-gap: 32px;
`;

const CoursesContainer = styled.div`
  display: grid;
  grid-template-columns: repeat(4, 296px);
  grid-column-gap: 16px;
  grid-row-gap: 24px;
`;

export default function App() {
  const [currTab, setCurrTab] = useState("트랙");
  const [searchValue, setSearchValue] = useState("");
  const [currPage, setCurrPage] = useState(0);
  const [cardData, setCardData] = useState([]);
  const [totalCardCount, setTotalCardCount] = useState(0);

  const handleClickTab = (tab) => {
    // 탭이 바뀌면 페이지네이션 페이지를 0으로 리셋
    if (tab !== currTab) {
      setSearchValue("");
      setCurrPage(0);
    };
    setCurrTab(tab);
  };

  const handleChangeSearch = (val) => {
    setSearchValue(val);
  };

  useEffect(() => {
    (async function () {
      if (currTab === "트랙") {
        // 페이지네이션이 바뀔 때 마다 offset이 url 바뀌도록
        const API_END_POINT = "https://api-xxxx/";
        const offset = currPage * 6;
        const trackUrl = `${API_END_POINT}track/list/?offset=${offset}&count=6`;
        const response = await axios.get(trackUrl);
        setTotalCardCount(response.data.track_count);
        setCardData(response.data.tracks);
      }

      if (currTab === "과목") {
        const API_END_POINT = "https://api-xxxx/";
        const offset = currPage * 8;
        const courseUrl = `${API_END_POINT}course/list/?offset=${offset}&count=8`;
        const response = await axios.get(courseUrl);
        setTotalCardCount(response.data.course_count);
        setCardData(response.data.courses);
      }
    })();
  }, [currTab, currPage]);

  return (
    <Container>
      <Tab currTab={currTab} onClick={handleClickTab} />
      <SearchTextField value={searchValue} onChange={handleChangeSearch} />
      <CardCount count={totalCardCount} />
      {currTab === "트랙" ? (
        <TracksContainer>
          {cardData.map((track, i) => (
            <TrackCard title={track.title} key={`track-card-${i}`} />
          ))}
        </TracksContainer>
      ) : (
        <CoursesContainer>
          {cardData.map((course, i) => (
            <CourseCard title={course.title} key={`course-card-${i}`} />
          ))}
        </CoursesContainer>
      )}
      <Pagination
        currPage={currPage}
        pageCount={Math.ceil(totalCardCount / (currTab === "트랙" ? 6 : 8))}
        onClickPage={setCurrPage}
      />
    </Container>
  );
}

 

📕 Pagination.js

import styled, { css } from "styled-components";
import Arrow from "./icons/Arrow.jsx";

const Wrapper = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 64px;
`;

const Button = styled.button`
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  line-height: 22px;
  color: #5e5f61;
  min-width: 24px;
  height: 24px;
  background: transparent;

  + button {
    margin-left: 4px;
  }

  ${(props) =>
    props.active &&
    css`
      color: #f9fafc;
      background: #524fa1;
      border-radius: 4px;
    `}
`;

const ArrowWrapper = styled.button`
  margin: ${(props) => (props.flip ? "0 0 0 15px !important" : "0 15px 0 0")};

  ${(props) =>
    props.flip &&
    css`
      transform: scaleX(-1);
    `}

  svg {
    display: block;
  }
`;

Pagination.defaultProps = {
  currPage: 0,
  pageCount: 5,
  onClickPage: () => {},
};

const MAX_PAGE_COUNT = 9;

function getPaginationArray(currentPage, total) {
  const resultList = [currentPage];

  let idx = 1;
  while (resultList.length < Math.min(MAX_PAGE_COUNT, total)) {
    if (currentPage - idx > -1) resultList.unshift(currentPage - idx);
    if (currentPage + idx < total) resultList.push(currentPage + idx);
    idx++;
  }

  return resultList;
}

export default function Pagination({ currPage, pageCount, onClickPage }) {
  return (
    <Wrapper>
      <ArrowWrapper
        onClick={() => currPage > 0 && onClickPage(currPage - 1)}
        disabled={currPage <= 0}
      >
        <Arrow />
      </ArrowWrapper>
      {getPaginationArray(currPage, pageCount).map((page) => {
        return (
          <Button
            onClick={() => onClickPage(page)}
            key={`page-button-${page}`}
            active={page === currPage}
          >
            {page + 1}
          </Button>
        );
      })}
      <ArrowWrapper
        flip
        onClick={() => currPage < pageCount - 1 && onClickPage(currPage + 1)}
        disabled={currPage >= pageCount - 1}
      >
        <Arrow />
      </ArrowWrapper>
    </Wrapper>
  );
}
반응형