react × Rails SPAの作成にあたりページネーションを導入することになった。
いろいろ調べた限りreact-paginateというnpmのライブラリを使うのが早そうだったのでやってみた。
その時の導入手順まとめです。
ゴール
今回は以下のgifのように書籍の一覧をページネーションを用いて12冊ずつ表示する、ということをします。
npm installで導入
以下のコマンドを実行しましょう
npm install react-paginate
package.jsonに以下の記述が入ります。
"dependencies": {
"@babel/preset-env": "^7.13.9",
"@babel/preset-react": "^7.12.13",
"file-loader": "^6.2.0",
"moment": "^2.29.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-paginate": "^7.1.2", //これ
"styled-components": "^5.2.1",
"universal-cookie": "^4.0.4"
}
モジュールのimport
場所…ページネーションを使いたいコンポーネント
今回は一覧表示をするIndex.jsxにて使います。
// paginateの読み込み
import ReactPaginate from 'react-paginate';
この1行でOKです
importしたReactPaginateを表示する
場所…モジュールをimportしたコンポーネント内のページネーションリンクを表示させたい箇所
以下のように記述します。クソ長いですね。コメントアウトでこれらのプロパティの意味を解説しましたので合わせて確認ください。
<ReactPaginate
pageCount={Math.ceil(this.state.books.length / this.state.perPage)} //総ページ数。今回は一覧表示したいデータ数 / 1ページあたりの表示数としてます。
marginPagesDisplayed={2} //先頭と末尾に表示するページの数。今回は2としたので1,2…今いるページの前後…後ろから2番目, 1番目 のように表示されます。
pageRangeDisplayed={5} //上記の「今いるページの前後」の番号をいくつ表示させるかを決めます。
onPageChange={this.pageChange} //ページネーションのリンクをクリックしたときのイベント(詳しくは下で解説します)
containerClassName='pagination' //ページネーションリンクの親要素のクラス名
pageClassName='page-item' //各子要素(li要素)のクラス名
pageLinkClassName='page-link' //ページネーションのリンクのクラス名
activeClassName='active' //今いるページ番号のクラス名。今いるページの番号だけ太字にしたりできます
previousLabel='<' //前のページ番号に戻すリンクのテキスト
nextLabel='>' //次のページに進むボタンのテキスト
previousClassName='page-item' // '<'の親要素(li)のクラス名
nextClassName='page-item' //'>'の親要素(li)のクラス名
previousLinkClassName='page-link' //'<'のリンクのクラス名
nextLinkClassName='page-link' //'>'のリンクのクラス名
disabledClassName='disabled' //先頭 or 末尾に行ったときにそれ以上戻れ(進め)なくするためのクラス
breakLabel='...' // ページがたくさんあるときに表示しない番号に当たる部分をどう表示するか
breakClassName='page-item' // 上記の「…」のクラス名
breakLinkClassName='page-link' // 「…」の中のリンクにつけるクラス
/>
プロパティの意味は公式ドキュメントにも詳しく解説されています↓
https://www.npmjs.com/package/react-paginate
page番号を変更した際の挙動を作る
やることは3つです。
①stateで先頭の番号を管理する
class Index extends React.Component {
constructor(props) {
super(props)
this.state = {
books: [],
start: 0, //最初は0番目(=最新)の要素から
perPage: 12 //1ページには12冊表示
}
}
今回はこんな感じでstartというstateを設定しています。
ついでに1ページあたり表示させる数もstateで管理します。
②onPageChangeイベントでstartの値を変更させる
以下のようなメソッドを定義しました。
pageChange(data) {
let pageNumber = data['selected']; //選択されたページ番号
this.setState({
//スタート位置をページ番号 * 1ページあたりの数、とする(例えば2番を選ぶと12 * 1で12番が先頭になる、つまり13番目以降の書籍が表示される)
start: pageNumber * this.state.perPage
})
}
pageNumberには引数で渡したdataのslectedというキーが入ります。
このキーには(ページ番号 - 1)にあたる数字が入ります。
(例えば、「1」をクリックした場合は0, 「2」をクリックした場合は1、といった感じ)
次にこの数字を使って先程定義したstartというstateを変更します。
pageNumber * perPage(今回は12)とするとstartの番号が変化します。
例えば、2ページ目に遷移すると1 * 12で12となります。
③sliceメソッドを使って配列の一部のみを表示する
ポイントはsliceメソッドの引数にstateで管理しているstartを渡すことです。
<BookList>
{/* 12冊ずつ表示。this.state.startは(ページ番号 - 1) * 12 */}
{this.state.books.slice(this.state.start, this.state.start + this.state.perPage).map(book => {
return (
<li key={book.isbn} className="book-list-item">
<img src={book.image_url}/>
<p className="book-title">{book.title}</p>
<p className="book-author">{book.author}</p>
<Link to="/">アウトプット一覧</Link>
</li> //returnがないと表示できない
)
})}
</BookList>
sliceメソッドの引数は(先頭, 末尾)となるため
先頭にstart、末尾にstartの値 + 1ページあたりの表示数
とすることでページ番号に応じて表示数は変えずに表示する内容を変えることができます。
感想
プロパティの設定を理解するのに苦労しました。多すぎでしょ…