yikegaya’s blog

仕事関連(Webエンジニア)と資産運用について書いてます

custom hooksを使ったReactでのページネーション実装

Reactでのページネーション実装調べてたんだけど、Railsのkaminariみたいに「これ使っとけば間違いない」的なライブラリや実装が見つからなかったのでcustom hooks作ってオレオレ実装してみた。

前提

  • React(Next.js)での実装
  • フロントエンドとバックエンドは分離していてaxiosでバックエンドからjsonをgetして内容をrenderしている
  • react-bootstrapライブラリを使ってBootstrapのページネーションを使っている

実装

  • 現在のページと最後のページを保持するstateを作る
  • そのstateを引数に取ってページネーションの番号を判定するカスタムフックを作る

顧客一覧を表示するページだとすると大体こんなん

マジックナンバー使っちゃってたりとリファクタリングの余地はあると思うけどだいたいこんな感じで切り出しとけば各componentで使い回せそう

pages/customers/index.tsx

import React, { useEffect, useState } from 'react'
import { useCookies } from 'react-cookie'
import { Pagination } from 'react-bootstrap'
import axios from 'axios'
import { useRouter } from 'next/router'
import { CustomerParam } from 'interfaces/CustomerParam'
import { usePaginationNumber } from 'hooks/usePaginationNumber'

const Index = (): JSX.Element => {
  const [cookies] = useCookies(['_hoge_session'])
  const router = useRouter()
  const [customers, setCustomers] = useState<CustomerParam[]>([])
  // Pagination用
  // 表示するレコード数
  const displayCount = 3
  const [currentPage, setCurrentPage] = useState(1)
  const [lastPage, setLastPage] = useState(1000)
  let usePaginationNumberReturnVal = usePaginationNumber(currentPage, lastPage)
  let firstPaginationNum: number = usePaginationNumberReturnVal[0]
  let secondPaginationNum: number = usePaginationNumberReturnVal[1]
  let thirdPaginationNum: number = usePaginationNumberReturnVal[2]
  let forthPaginationNum: number = usePaginationNumberReturnVal[3]
  let fifthPaginationNum: number = usePaginationNumberReturnVal[4]

  useEffect(() => {
    const fetchCustomers = () => {
      axios.get(
        `${process.env.BACKEND_URL}/api/internal/customers`, {
          headers: {
            'Session-Id': cookies._hoge_session
          },
          params: {
            current_page: currentPage,
            display_count: displayCount
          }
        }
      )
      .then(function (response) {
        setCustomers(response.data.customers)
        setLastPage(response.data.last_page)
      })
      .catch(error => {
        console.log(error)
      })
    }
    fetchCustomers()
  }, [router.query.public_id, cookies._hoge_session, currentPage, lastPage])


  return (
    <>
      {customers.map((customer, i) => {
        return (
          <div key={i}>
            {customer.name}
          </div>
        )
      })}
      <Pagination>
        <Pagination.First onClick={() => setCurrentPage(1)} />
        {currentPage > 1 && <Pagination.Prev
          onClick={() => setCurrentPage(currentPage - 1)} />}
        <Pagination.Item
          active={currentPage == firstPaginationNum}
          onClick={() => setCurrentPage(firstPaginationNum)}>{firstPaginationNum}</Pagination.Item>
        {lastPage > 1 && <Pagination.Item
          active={currentPage == secondPaginationNum}
          onClick={() => setCurrentPage(secondPaginationNum)}>{secondPaginationNum}</Pagination.Item>}
        {lastPage > 2 && <Pagination.Item
          active={currentPage == thirdPaginationNum}
          onClick={() => setCurrentPage(thirdPaginationNum)}>{thirdPaginationNum}</Pagination.Item>}
        {lastPage > 3 && currentPage < lastPage &&  <Pagination.Item
          active={currentPage == forthPaginationNum}
          onClick={() => setCurrentPage(forthPaginationNum)}>{forthPaginationNum}</Pagination.Item>}
        {lastPage > 4 && currentPage < lastPage - 1 && <Pagination.Item
          active={currentPage == fifthPaginationNum}
          onClick={() => setCurrentPage(fifthPaginationNum)}>{fifthPaginationNum}</Pagination.Item>}
        {currentPage !== lastPage && <Pagination.Next
          onClick={() => setCurrentPage(currentPage + 1)} />}
        <Pagination.Last onClick={() => setCurrentPage(lastPage)} />
      </Pagination>
    </>
  )
}

export default Index

hooks/usePaginationNumber

import { useEffect, useState } from 'react'

export const usePaginationNumber = (currentPage: number, lastPage: number) => {
  const [firstPaginationNum, setFirstPaginationNum] = useState(1)
  const [secondPaginationNum, setSecondPaginationNum] = useState(2)
  const [thirdPaginationNum, setThirdPaginationNum] = useState(3)
  const [forthPaginationNum, setForthPaginationNum] = useState(4)
  const [fifthPaginationNum, setFifthPaginationNum] = useState(5)

  useEffect(() => {
    setFirstPaginationNum(currentPage < 3 ? 1 : currentPage - 2)
    setSecondPaginationNum(currentPage < 4 ? 2 : currentPage - 1)

    let thirdNum = 3
    if (currentPage === 1) {
      thirdNum = currentPage + 2
    } else if (currentPage === 2) {
      thirdNum = currentPage + 1
    } else if (currentPage === lastPage - 1) {
      thirdNum = lastPage - 1
    } else if (currentPage === lastPage) {
      thirdNum = lastPage
    } else {
      thirdNum = currentPage
    }
    setThirdPaginationNum(thirdNum)

    let forthNum = 4
    if (currentPage === 1 || (currentPage === 2 && lastPage > 5)) {
      forthNum = 4
    } else if (currentPage === 2) {
      forthNum = 5
    } else if (currentPage === lastPage - 2) {
      forthNum = lastPage - 1
    } else {
      forthNum = currentPage + 1
    }
    setForthPaginationNum(forthNum)

    let fifthNum = 5
    if (currentPage === 1 || (currentPage === 2 && lastPage > 5)) {
      fifthNum = 5
    } else if (currentPage === 2) {
      fifthNum = 6
    } else if (currentPage === lastPage) {
      fifthNum = lastPage
    } else {
      fifthNum = currentPage + 2
    }
    setFifthPaginationNum(fifthNum)
  }, [currentPage, lastPage])

  return [
    firstPaginationNum,
    secondPaginationNum,
    thirdPaginationNum,
    forthPaginationNum,
    fifthPaginationNum
  ]
}