결과 화면

 


수정사항

https://jae-study.tistory.com/85

기존에는 react-js-pagination 패키지를 사용해 페이지네이션을 구현했다.

하지만 페이지네이션을 프론트에서 만들었을 경우, 서버와 데이터베이스의 부하가 발생할 수 있기 때문에 백앤드의 작업이 필요하다.

백앤드 없이 프론트에서 페이지네이션을 구현하고 싶다면 위의 링크를 참고한다.

 

https://jae-study.tistory.com/138

NestJs로 페이지네이션을 만들었다.

페이지네이션을 백앤드에서 만들어야 하는 이유에 대한 설명은 위의 링크를 참고한다.

 


1. boardReducer.tsx

  • src/store/boardReducer.tsx
  1. page 파라미터를 받는다. 타입스크립트이기 때문에 타입을 정의한다.
  2. axios.get을 사용해 page에 해당하는 데이터를 요청한다. URL은 쿼리 파라미터가 붙은 key=value 형태이다.
export const getBoardList = createAsyncThunk(
  'GET_BOARD_LIST',
  async (page: number) => {
    try {
      const response = await axios.get(`http://localhost:3001/board?page=${page}`)
      return response.data
    } catch (error) {
      console.log(error)
    }
  }
)

...

 

2. 페이지네이션에 해당하는 데이터 가져오기

  • src/pages/board/list/index.tsx
  1. dispatch를 사용해 page에 해당하는 게시판 데이터를 가져온다.
  2. axios를 통해 받아온 데이터가 객채(object) 형태이기 때문에 (배열처럼 보이지만 typeof로 확인하면 object이다.) [board, setBoard] state 변수를 만들고, 전개 연산자를 사용해 배열 형태로 바꾼다.
  3. axios를 통해 받아온 page 데이터가 문자(string) 형태이기 때문에 [page, setPage] state 변수를 만들고, Number로 타입을 변환한다.
  4. 페이지네이션 버튼에 대한 공통 함수(handlePagination)를 만든다.
import { useEffect, useState } from 'react'
import { useAppDispatch, useAppSelector } from '../../../hooks/useApp'
...

const BoardList = () => {
  // ** Hooks
  const dispatch = useAppDispatch()

  // ** Redux States
  const boardList = useAppSelector(state => state.boardReducer)

  // ** States
  const [board, setBoard] = useState<boardType[]>([])
  const [page, setPage] = useState<number>(1)

  // 페이지네이션
  function handlePagination(newPage: number) {
    if(newPage >= 1 && newPage <= boardList.meta.last_page) {
      dispatch(getBoardList(newPage))
    }
  }

  // 페이지가 로딩되면 게시판 리스트 가져옴
  useEffect(() => {
    dispatch(getBoardList(page))
  }, [])

  // 페이지가 로딩된 후 state 변수에 boardList 데이터 저장함
  useEffect(() => {
    if (boardList && boardList.data) {
      setBoard([...boardList.data])
      setPage(Number(boardList.meta.page))
    }
  }, [boardList])

  return (
    ...
  )
}

export default BoardList

 

3. 마크업 하기

  • src/pages/board/list/index.tsx.
  1. 삼항연산자를 사용해 데이터가 있고, 없을 때 마크업을 다르게 표현한다.
  2. map 메서드를 사용해 게시판 데이터를 보여준다.
  3. 처음으로, 이전, 다음, 마지막으로 버튼 기능을 구현한다.
const BoardList = () => {
  ...

  return (
    <div className="board-list">
      <Title children="Board list"/>

      <h4>Total post : {boardList.meta?.total}</h4>

      <table>
        <colgroup>
          <col width="15%"/>
          <col width="65%"/>
          <col width="20%"/>
        </colgroup>

        <thead>
          <tr>
            <th>No</th>
            <th>Title</th>
            <th>Date</th>
          </tr>
        </thead>

        <tbody>
          {
            board.length === 0 ? (
              <tr>
                <td colSpan={3}>데이터가 없습니다.</td>
              </tr>
            ) : (
              board.map((board, index) => {
                return (
                  <tr key={index}>
                    <td>{board.id}</td>
                    <td className="title">
                      <Link to={`/board/${board.id}`}>{board.title}</Link>
                    </td>
                    <td>{dayjs(board.created_at).format('YYYY.MM.DD')}</td>
                  </tr>
                )
              })
            )
          }
        </tbody>
      </table>

      {
        board.length === 0 ? (
          <></>
        ) : (
          <div className="pagination">
            {
              boardList.meta.last_page === 1 ? (
                <>
                  <button disabled>&#60;</button>
                  <p>{page}</p>
                  <button disabled>&#62;</button>
                </>
              ) : (
                <>
                  <button onClick={() => handlePagination(1)}>&#60;&#60;</button>
                  <button onClick={() => handlePagination(page - 1)}>&#60;</button>
                  <p>{page} / {boardList.meta.last_page}</p>
                  <button onClick={() => handlePagination(page + 1)}>&#62;</button>
                  <button onClick={() => handlePagination(boardList.meta.last_page)}>&#62;&#62;</button>
                </>
              )
            }
          </div>
        )
      }

      <Link to="/board/create">
        <Button children="Write" variant="primary"/>
      </Link>
    </div>
  )
}

export default BoardList

목표

  • react-js-pagination 패키지를 사용해서 페이지네이션 컴포넌트 만들기
  • 페이지네이션 기능 구현

 


결과 화면

 


1. react-js-pagination 패키지 설치하기

https://www.npmjs.com/package/react-js-pagination

react-js-pagination 패키지에 대한 자세한 설명은 npm 사이트를 참고한다.

 

타입스크립트로 설치된 리액트이기 때문에 타입스크립트 버전도 같이 설치한다.

yarn add react-js-pagination @types/react-js-pagination
또는
npm install react-js-pagination @types/react-js-pagination

 

2. Pagination 컴포넌트 css 또는 scss 작성하기

react-js-pagination 패키지의 경우, css가 없기 때문에 직접 css 또는 scss를 작성한다.

 

  • src/assets/scss/components/pagination.scss

css/scss를 작성하고, 공통 css/scss에 import 하거나 페이지네이션을 사용한 페이지에 import 한다.

해당 scss는 예시일 뿐, 원하는 디자인으로 만든다.

.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 5px;
  margin: 50px auto;

  li {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background-color: $bg-light;
    transition: all 0.3s;

    &:hover,
    &:active ,
    &.active{
      background-color: $bg-point;
      color: $white;
    }

    &:nth-child(1),
    &:nth-child(2),
    &:nth-last-child(1),
    &:nth-last-child(2) {
      a {
        align-items: baseline;
        font-size: 20px;
      }
    }

    a {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100%;
      height: 100%;
      font-size: 12px;
    }
  }
}

 

3. react-js-pagination 패키지의 Pagination 컴포넌트 사용하기

  • src/pages/board/list/index.tsx

axios로 받은 전체 게시글 데이터를 slice() 해서 페이지네이션 별로 화면에 보여준다.

 

  1. Pagination 컴포넌트를 import 한다.
  2. [currentPost, setCurrentPost], [page, setPage] state 변수를 만든다.
  3. postPerPage, indexOfLastPost, indexOfFirstPost 변수를 만든다.
  4. handlePageChange() 클릭 이벤트를 만든다. 페이지네이션을 누를 때마다 몇 번째 페이지인지 알 수 있다.
  5. 최근 게시물부터 역순으로 출력하고 싶기 때문에 axios 데이터를 reverse() 한다.
  6. 전체 게시글 또는 페이지네이션이 바뀔 때마다 동작해야 하기 때문에 useEffect() 안에 [setCurrentPost] state 변수를 작성한다.
  7. axios 데이터를 첫 번째 게시글 인덱스 번호와 마지막 게시글 인덱스 번호로 slice() 해서 5개씩 노출시킨다.
import Pagination from 'react-js-pagination'

const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([]) // axios에서 받아온 전체 게시글 데이터
  const [currentPost, setCurrentPost] = useState<BoardType[]>(boardList) // 페이지네이션을 통해 보여줄 게시글
  const [page, setPage] = useState<number>(1) // 현재 페이지 번호

  const postPerPage: number = 5 // 페이지 당 게시글 개수
  const indexOfLastPost: number = page * postPerPage
  const indexOfFirstPost: number = indexOfLastPost - postPerPage

  const handlePageChange = (page: number) => {
    setPage(page)
  }

  useEffect(() => {
    axios.get('http://localhost:3001/board')
      .then((response) => {
        setBoardList([...response.data].reverse())
      })

      .catch(function(error) {
        console.log(error)
      })
  }, [])

  useEffect(() => {
    setCurrentPost(boardList.slice(indexOfFirstPost, indexOfLastPost))
  }, [boardList, page])

  return (
    <div className="board-list">
      <table>
        ...
      </table>

      <Pagination
        activePage={page}
        itemsCountPerPage={postPerPage}
        totalItemsCount={boardList.length}
        pageRangeDisplayed={5}
        prevPageText={"‹"}
        nextPageText={"›"}
        onChange={handlePageChange}/>
    </div>
  )
}

export default BoardList

 


전체 코드

  • src/pages/board/list/index.tsx
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import dayjs from 'dayjs'
import Pagination from 'react-js-pagination'
import { BoardType } from '../../../interface/BoardType'
import Button from '../../../components/form/Button'
import Title from '../../../components/text/Title'
import './index.scss'

const BoardList = () => {
  // state
  const [boardList, setBoardList] = useState<BoardType[]>([]) // axios에서 받아온 게시글 데이터
  const [currentPost, setCurrentPost] = useState<BoardType[]>(boardList) // 게시판 목록에 보여줄 게시글
  const [page, setPage] = useState<number>(1) // 현재 페이지 번호

  const postPerPage = 5 // 페이지 당 게시글 개수
  const indexOfLastPost = page * postPerPage
  const indexOfFirstPost = indexOfLastPost - postPerPage

  const boardLength = boardList.length

  const handlePageChange = (page: number) => {
    setPage(page)
  }

  useEffect(() => {
    axios.get('http://localhost:3001/board')
      .then((response) => {
        setBoardList([...response.data].reverse())
      })

      .catch(function(error) {
        console.log(error)
      })
  }, [])

  useEffect(() => {
    setCurrentPost(boardList.slice(indexOfFirstPost, indexOfLastPost))
  }, [boardList, page])

  return (
    <div className="board-list">
      <Title children="Board list"/>

      <h4>Total post : {boardLength}</h4>

      <table>
        <colgroup>
          <col width="15%"/>
          <col width="65%"/>
          <col width="20%"/>
        </colgroup>

        <thead>
          <tr>
            <th>No</th>
            <th>Title</th>
            <th>Date</th>
          </tr>
        </thead>

        <tbody>
          {
            currentPost.map((board, index) => {
              return (
                <tr key={index}>
                  <td>{index + 1}</td>
                  <td className="title"><Link to={`/board/${board.id}`}>{board.title}</Link></td>
                  <td>{dayjs(board.created_at).format('YYYY.MM.DD')}</td>
                </tr>
              )
            })
          }
        </tbody>
      </table>

      <Pagination
        activePage={page}
        itemsCountPerPage={postPerPage}
        totalItemsCount={boardList.length}
        pageRangeDisplayed={5}
        prevPageText={"‹"}
        nextPageText={"›"}
        onChange={handlePageChange}/>

      <Link to="/board/create">
        <Button children="Write" variant="primary"/>
      </Link>
    </div>
  )
}

export default BoardList

 

#2.2
react-js-paginatnio 패키지를 설치하고,
scss를 작성해 디자인을 커스텀하고,
axios 데이터를 slice 해서
페이지네이션 기능이 동작하게 만들었다.

+ Recent posts