【Gatsby】paginationを実装した

June 22, 2022
Gatsby / React / GraphQL

このブログも徐々に記事数が増えてきたので、pagenationを追加しようということで、 paginationのライブラリが既に世の中にあるのですが、reactとGraphQLの勉強も兼ねてあえて自前で実装してみました。

repo

https://github.com/chanfuku/gatsby-blog

components/pagination.js

まず、paginationコンポーネントを作成します。

// components/pagination.js
import * as React from "react"
import { Link } from "gatsby"

const Pagination = ({ totalCount }) => {
  const PER_PAGE = 5

  const range = (start, end) => [...Array(end - start + 1)].map((_, i) => start + i)

  return (
    <ul class="pagenation">
      {range(1, Math.ceil(totalCount / PER_PAGE)).map((number, index) => (
        <li key={index}>
          <Link to={`/page/${number}`}>{number}</Link>
        </li>
      ))}
    </ul>
  )
}

export default Pagination

components/posts.js

次に記事一覧コンポーネントを作成します。

// src/components/posts.js
import * as React from "react"
import { Link } from "gatsby"

const Posts = ({ posts }) => {
  return (
      <ol style={{ listStyle: `none` }}>
        {posts.map(post => {
          const title = post.frontmatter.title || post.fields.slug

          return (
            <li key={post.fields.slug}>
              <article
                className="post-list-item"
                itemScope
                itemType="http://schema.org/Article"
              >
                <header>
                  <h2>
                    <Link to={post.fields.slug} itemProp="url">
                      <span itemProp="headline">{title}</span>
                    </Link>
                  </h2>
                  <small>{post.frontmatter.date}</small>
                </header>
                <section>
                  <p
                    dangerouslySetInnerHTML={{
                      __html: post.frontmatter.description || post.excerpt,
                    }}
                    itemProp="description"
                  />
                </section>
              </article>
            </li>
          )
        })}
      </ol>
  )
}

export default Posts

templates/blog-page.js

次にtemplates/blog-page.jsを作成します。

import * as React from "react"
import { graphql } from "gatsby"
import Bio from "../components/bio"
import Layout from "../components/layout"
import Seo from "../components/seo"
import Pagination from "../components/pagination"
import Posts from "../components/posts"

const BlogPage = ({ data, location }) => {
  const siteTitle = data.site.siteMetadata?.title || `Title`
  const posts = data.allMarkdownRemark.nodes

  return (
    <Layout location={location} title={siteTitle}>
      <Seo title={siteTitle} />
      <Bio />
      <Posts posts={posts} />
      <Pagination totalCount={data.allMarkdownRemark.totalCount} />
    </Layout>
  )
}

export default BlogPage

export const query = graphql`
  query ($limit: Int!, $skip: Int!) {
    site {
      siteMetadata {
        title
        description
      }
    }
    allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC }
      skip: $skip
      limit: $limit
      ) {
      totalCount
      nodes {
        excerpt
        fields {
          slug
        }
        frontmatter {
          date(formatString: "MMMM DD, YYYY")
          title
          description
        }
      }
    }
  }
`

pages/index.js

次にpages/index.jsに上で作成したPaginationとPostsを追加します。

import Pagination from "../components/pagination"
import Posts from "../components/posts"
.
. // 省略
.
  return (
    <Layout location={location} title={siteTitle}>
      <Seo title={siteTitle} />
      <Bio />
      <Posts posts={posts} />
      <Pagination totalCount={data.allMarkdownRemark.totalCount} />
    </Layout>
  )

graphQLに関しては、skip, limitを引数に追加し、totalCountを返すように修正します。 skipはMysqlで言うところのoffset、つまりn件目から取得するという意味です。limitは取得したい件数で、totalCountは全件数です。

    allMarkdownRemark(
      sort: { fields: [frontmatter___date], order: DESC }
      skip: 0
      limit: 5
    ) {
      totalCount

上記のコードはrepositoryの index.js にあります。

gatsby-node.js

次にgatsby-node.jsを修正します。 まず、createPagesの中に、以下のようにgraphqlにtotalCount(全件数)を返すように定義を追加します。

  const result = await graphql(
    `
      {
        allMarkdownRemark(
          sort: { fields: [frontmatter___date], order: ASC }
          limit: 1000
        ) {
          totalCount
          nodes {
            id
            fields {
              slug
            }
          }
        }
      }
    `
  )

そして、1ページ目の記事一覧ページ、2ページ目の記事一覧ページ…を作成する処理を実装します。

  const posts = result.data.allMarkdownRemark.nodes
  const PerPage = 5
  const pageCount = Math.ceil(result.data.allMarkdownRemark.totalCount / PerPage)

  for (let i = 0; i < pageCount; i++) {
    createPage({
      path: `/page/${i + 1}`,
      component: path.resolve("./src/templates/blog-page.js"),
      context: {
        limit: PerPage,
        skip: i * PerPage,
      },
    })
  }

上記のコードはrepositoryの gatsby-node.js にあります。

src/pagenation.css

pagenationのcssを作成します。

.pagenation {
 display: flex;
}

.pagenation li {
  list-style: none;
  margin-left: 20px;
  border-radius: 50%;
  background-color: skyblue;
  height: 20px;
  width: 20px;
  text-align:center;
  line-height: 20px;
}

gatsby-browser.js

最後にpagenation.cssをimportします。

import "./src/pagenation.css"

完成

Image1 まだ記事数がそこまでないので、最初のページに戻る、最後のページに進むの機能は保留で。

次はMaterial UIを入れてページネーションをもう少しカッコよくしたいと思います。


Profile picture

React, Vue, TypeScript, Node.js, PHP, Laravel, AWS, Firebase, Docker, GitHub Actions, etc...