かめ。ブログ

フロントエンドエンジニアが「Web」「Youtube」「3D」「お金」「お買い物情報」など、興味があることについて深堀っていくブログです。

Next.js × microCMSでページング「react-paginate」を使っての実装方法

2021年9月21日

5 日前

前提

必要なもの

microcms-js-sdk

npm

npm install microcms-js-sdk

yarn

yarn add microcms-js-sdk

react-paginate

npm

npm install react-paginate

yarn

yarn add react-paginate

PagingComponentを作る(styled-componentsと合わせて使う)

Pagination.tsx

import React from 'react'
import Router from 'next/router'
import ReactPaginate from 'react-paginate'
import styled from 'styled-components'
import { LIST_LIMIT } from '~/const/api'

const PageContainer = styled.div`
  margin: 24px 0;
  & ul {
    display: flex;
    justify-content: center;
    font-size: 14px;
  }
  & li {
    list-style-type: none;
    margin: 0 4px;
    cursor: pointer;
    transition: opacity 0.2s ease;
    &:hover {
      opacity: 0.6;
    }
  }
  & .previous a, .next a {
    display: block;
    margin-top: 15px;
    width: 16px;
    height: 16px;
    text-indent: -1000px;
    transform: rotate(45deg);
    overflow: hidden;
  }
  & .previous a {
    border-left: 1px solid #39c;
    border-bottom: 1px solid #39c;
  }
  & .next a {
    border-top: 1px solid #39c;
    border-right: 1px solid #39c;
  }
  & li.selected {
    pointer-events: none;
  }
  & li.selected a {
    background-color: #39c !important;
    color: #fff !important;
  }
  & li:not(.previous,.next) a {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border: 1px solid #39c;
    color: #39c;
  }
  & li.break a {
    border: none;
  }
  .disabled {
    opacity: 0.2;
    pointer-events: none;
  }

  @media (max-width: 600px) {
    & .previous a, .next a {
      margin-top: 13px;
      width: 8px;
      height: 8px;
    }
    & li:not(.previous,.next) a {
      width: 32px;
      height: 32px;
    }
  }
`

interface PaginationProps {
  rootPath?: string
  totalCount: number
  currentNum: number
}

const ONE_PAGE_DISPLAY_USERS = LIST_LIMIT
const LAST_DISPLAY_SIZE = LIST_LIMIT
const AROUND_DISPLAY_PAGES = Math.floor(LIST_LIMIT / 2)

const Pagination: React.FC<PaginationProps> = props => {
  const handlePaginate = (selectedItem: { selected: number }) => {
    if(selectedItem.selected === 0) {
      Router.push('/')
    } else {
      Router.push(`${props.rootPath || ''}/page/${selectedItem.selected}`)
    }
  }

  return (
    <PageContainer>
      <ReactPaginate
        pageCount={Math.ceil(props.totalCount / ONE_PAGE_DISPLAY_USERS)}
        initialPage={props.currentNum}
        marginPagesDisplayed={LAST_DISPLAY_SIZE}
        pageRangeDisplayed={AROUND_DISPLAY_PAGES}
        onPageChange={handlePaginate}
      />
    </PageContainer>
  )
}

export default Pagination

styled-componentsを使う場合は下記に注意。
下記のように書くやり方ですと、できないようなので、PageContainerで囲ってあげています。

const StyledReactPaginate = styled(ReactPaginate)`
  a {
    cursor: pointer;
  }
  .selected {
    background: lime;
  }
`

https://github.com/AdeleD/react-paginate/issues/321

react-paginateで、使っているオプション解説

名前タイプ説明
pageCountNumber必須。総ページ数
pageRangeDisplayedNumber必須。表示されるページの範囲
marginPagesDisplayedNumber必須。余白に表示するページ数
initialPageNumberデフォルトの選択ページ位置
onPageChangeFunctionページをクリックした時に呼び出す関数

ページ用のコンポーネント作成(ページングを使う側)

api.ts

export const LIST_LIMIT =  10
export const LIST_OFFSET = 10

client.js

import { createClient } from 'microcms-js-sdk';

export const client = createClient({
  serviceDomain: process.env.DOMAIN,
  apiKey: process.env.API_KEY || '',
})

[page].tsx(一部抜粋)

import type { NextPage } from 'next'
import { LIST_LIMIT, LIST_OFFSET } from '../const/api'
import Pagination from '../components/common/Pagination'

interface Home {
  entries: EntriesApi
  page: number
}

const Home: NextPage<Home> = (props) => {
  return (
      <main>
        <article>
            <div>
              <Pagination {...{
                totalCount: props.entries.totalCount,
                currentNum: props.page
              }}/>
            </div>
        </article>
      </main>
  )
}

import { client } from '~/utils/client';
import { GetStaticProps, GetStaticPaths } from 'next'
export const getStaticPaths: GetStaticPaths = async () => {
  const { totalCount } = await client.get<EntriesApi>({
    endpoint: 'entries',
    queries: {
      offset: 0,
      limit: 0,
      fields: 'id'
    }
  })

  const paths = [...Array(Math.ceil(totalCount / LIST_LIMIT))]
    .map((_, i) => i)
    .map((page) => `/page/${page}`)

  return { paths, fallback: false }
}

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const page = parseInt(String(params?.page)) || 0;

  const [entries] = await Promise.all([
    client.get<EntriesApi>({
      endpoint: 'entries',
      queries: {
        orders: '-publishedAt',
        offset: page * LIST_OFFSET,
        limit: LIST_LIMIT,
        fields: 'id,title,categories,tags,publishedAt,image,description'
      }
    })
  ])


  return {
    props: {
      entries,
      page
    },
  };
};

getStaticPathsではoffset: 0,limit: 0で取得しています。
ここではtotalCountが欲しいだけなので、実際にリストは取得しないようにしています
page * LIST_OFFSETとしてるところが少し注意です。
microcmsの仕様的にはoffsetの位置からlimitの位置までのものを取得という考え方です。
ですのでpage数をそのまま与えても、うまくいかないのでページ数かける表示されてる数を渡してあげることで意図した動作になります。
例: ページ数(2) * 表示数(10) = 20 〜 リストのリミット数(10)が表示

interface EntriesApi

export interface ListApi {
  totalCount: number
  offset: number
  limit: number
}

export interface EntriesApi extends ListApi {
  contents: Entry[]
}


参考


まとめ

ページング処理も毎回つくると大変なので横着できるところはプラグインを使ってサクッと作っていけると良いですね!

おすすめの本

Javascriptを勉強する際に読んでおくと良いなと思った本たちです。
特にリーダブルコードは綺麗なコードを書く考え方が身に付きます。いまでもその考え方が根付いています。

よくみられてる記事