개발/프로젝트

Material UI를 사용하여 페이지네이션 구현하기

xuwon 2025. 2. 13. 19:00

이번에는 MUIPagination 컴포넌트를 사용해서 페이지네이션을 구현해 보았다.
ThemeProvider를 통해 스타일도 커스텀해 보았다.

 

 

React Pagination component - Material UI

The Pagination component enables the user to select a specific page from a range of pages.

mui.com

↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
참고했던 것

 

 

// theme.ts

import { createTheme } from "@mui/material/styles";

const theme = createTheme({
  breakpoints: {
    values: {
      xs: 0,     // 모바일
      sm: 600,   // 기존 small
      md: 769,   // 새로운 breakpoint 추가!
      lg: 900,   // 기존 medium
      xl: 1200,  // 기존 large
    },
  },
});

export default theme;

MUITailwind CSSbreakpoint가 달랐다.
그래서 똑같은 md여도 tailwind는 768 이상이고 MUI는 900 이상...

스타일이 안 맞아서 createTheme()으로 bp를 새로 추가해 주었다.

이렇게 커스텀한 스타일은 ThemeProviderPagination 컴포넌트를 감싸면 Pagination에 적용이 가능하다.

 


 

"use client";

import { ReactNode, useState } from "react";
import { Pagination, ThemeProvider } from "@mui/material";
import theme from "./theme";

interface PaginationsProps {
  children: ReactNode; // children으로 컴포넌트 받을 예정
}

// ✅ 더미 데이터 (1920개의 아이템)
const allItems = Array.from({ length: 1920 }, (_, i) => `Item ${i + 1}`);

const PaginationList: React.FC<PaginationsProps> = ({ children }) => {
  const [page, setPage] = useState(1); // 현재 페이지
  const itemsPerPage = 8; // 한 페이지에 보여줄 개수

  // 더미데이터가 현재 배열이므로 인덱스 사용
  // 페이지 1 → startIndex = 0, endIndex = 8 → Item 1 ~ Item 8
  // 페이지 2 → startIndex = 8, endIndex = 16 → Item 9 ~ Item 16
  const startIndex = (page - 1) * itemsPerPage;
  const endIndex = startIndex + itemsPerPage;
  const displayedItems = allItems.slice(startIndex, endIndex);

  // 페이지 변경 핸들러
  const handleChange = (event: React.ChangeEvent<unknown>, value: number) => {
    setPage(value); // 클릭된 페이지를 현재 페이지로 업데이트
  };

  return (
    <ThemeProvider theme={theme}>
      {/* 현재 페이지의 데이터 렌더링 */}
      {displayedItems.map((item, index) => (
        <div key={index}>
          {item} {/* 임시 */}
          {children}
        </div>
      ))}

      {/* ✅ 페이지네이션 UI */}
      <Pagination
        count={Math.ceil(allItems.length / itemsPerPage)} // 전체 페이지 개수 (전체 아이템 / 페이지 당 아이템)
        page={page}
        onChange={handleChange}
        sx={{
          "& .MuiPaginationItem-root": {
            fontFamily: "var(--font-nanumsquare)",
            color: "#6A6A6A", // 기본 페이지 텍스트 색상: main-text
            fontSize: { xs: "12px", md: "16px" },
            margin: "0 2px",
          },
          "& .MuiPaginationItem-root:hover": {
            backgroundColor: "#ffd69588", // bg-yellow-1에 투명도 적용
          },
          "& .MuiPaginationItem-page.Mui-selected:hover": {
            backgroundColor: "#ffd69588",
          },
          "& .MuiPaginationItem-page.Mui-selected": {
            backgroundColor: "#FFE7BF", // bg-yellow-2
            border: "1px solid #EFC071", // main-yellow
          }, // 선택된 페이지 색상
          "& .MuiPaginationItem-previousNext": { color: "#EFC071" }, // 이전/다음 버튼 색상
        }}
        className="pt-4"
      />
    </ThemeProvider>
  );
};

export default PaginationList;

 

interface PaginationsProps {
  children: ReactNode; // `children`으로 컴포넌트를 받을 예정
}

Swiper 라이브러리 사용 때와 똑같이 Props 타입을 먼저 지정해 주었다.
컴포넌트를 페이지네이션 할 거니까 ReactNode로!

 

const [page, setPage] = useState(1); // 현재 페이지 상태
const itemsPerPage = 8; // 한 페이지에 보여줄 개수

현재 페이지를 상태로 관리하고, 한 번에 8개의 아이템을 표시하도록 설정했다.
(생각해보니 페이지마다 표시하기로 한 아이템 수가 달라서, 이것도 따로 props로 받아주어야 할 것 같다. 수정해야지)

 

const allItems = Array.from({ length: 1920 }, (_, i) => `Item ${i + 1}`);

그리고 확인을 위해 더미 데이터를 생성했다.

 

const startIndex = (page - 1) * itemsPerPage; // 시작 인덱스
const endIndex = startIndex + itemsPerPage;   // 끝 인덱스
const displayedItems = allItems.slice(startIndex, endIndex);

그리고 slice()를 통해 현재 페이지에서 보여줄 데이터만 잘라서 가져오도록 했다.

페이지 1 → startIndex = 0, endIndex = 8 → Item 1 ~ Item 8
페이지 2 → startIndex = 8, endIndex = 16 → Item 9 ~ Item 16

요렇게 된다.

 

const handleChange = (event: React.ChangeEvent<unknown>, value: number) => {
  setPage(value);
};

클릭된 페이지 값을 setPage(value)로 업데이트 한다.

page가 바뀌면 선택한 페이지에 맞게 displayedItems가 자동으로 변경된다!

 

{displayedItems.map((item, index) => (
  <div key={index}>
    {item} {/* 현재 페이지의 아이템 표시 */}
    {children} {/* children을 포함하여 추가적인 UI 렌더링 가능 */}
  </div>
))}

아까 만든 더미 데이터를 map()을 사용해서 렌더링 한다.

{children}을 추가해서 외부에서 받아온 컴포넌트가 각 아이템 아래 렌더링 되도록 했다.

 

<Pagination
  count={Math.ceil(allItems.length / itemsPerPage)} // 전체 페이지 개수
  page={page} // 선택된 페이지
  onChange={handleChange} // 클릭 시 페이지 변경
  sx={{ // 스타일
    "& .MuiPaginationItem-root": {
      fontFamily: "var(--font-nanumsquare)",
      color: "#6A6A6A",
      fontSize: { xs: "12px", md: "16px" },
      margin: "0 2px",
    },
    "& .MuiPaginationItem-root:hover": {
      backgroundColor: "#ffd69588",
    },
    "& .MuiPaginationItem-page.Mui-selected:hover": {
      backgroundColor: "#ffd69588",
    },
    "& .MuiPaginationItem-page.Mui-selected": {
      backgroundColor: "#FFE7BF",
      border: "1px solid #EFC071",
    },
    "& .MuiPaginationItem-previousNext": { color: "#EFC071" },
  }}
  className="pt-4"
/>

이것도 그냥 하라는대로 하면 된다.
마찬가지로 스타일에 들어가는 클래스명은 기본 제공되는 거다!

지금은 더미 데이터지만 API 데이터를 받아와서 넣어줄 예정이다.