React + Laravel のページネーションについて

# React + Laravel のページネーションについて

初めまして、Qiita初投稿です！

```$ php artisan preset react```
スカフォールドをvueからreactに切り替えている前提でお話します。

今回は以下のような地方のイベント一覧のサンプルを作成しました。
<img width="645" alt="スクリーンショット 2019-01-28 14.51.29.png" src="https://qiita-image-store.s3.amazonaws.com/0/29894/5badc064-eadb-b1c6-8067-a0931490d2ef.png">




###lalavelのページネーション機能について
今回laravel側のAPI実装についての説明は端折らせて頂きますが一応ページネーション機能についてはざっとおさらいします。
クエリビルダやEloquentの最後にpagenateメソッドを追加すると簡単に実装できます。

Eloquentの場合：モデル名::get()->paginate(1ページあたり表示させたい行数)


作成したAPIにリクエストを送ると
<img width="426" alt="スクリーンショット 2019-01-28 15.25.28.png" src="https://qiita-image-store.s3.amazonaws.com/0/29894/2e5f8410-64de-825f-8c80-dd25db50b60a.png">


↑のようなJSONが返ってきます。
実際のデータはdataプロパティの中にpaginateで指定した数だけ入っています。

<img width="497" alt="スクリーンショット 2019-01-28 15.33.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/29894/91d860e4-b03d-c42e-1bbe-f8260d14a056.png">
表示に使用するキーはid,title,image_path_start_date,addresのみです。


以下のメタ情報を使ってreactでページネーションを実装していきましょう。

| キー | 内容 |
|:-:|:-:|
| current_page  | 現在読み込んでいるページ  |
|  per_page | 1ページあたりの取得件数(今回は6)  |
|  total |  総件数 |



##reactでのview実装


「react-js-pagination」ライブラリを使用して実装してみました。


```react:Example.js
import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import Card from './Card';
import axios from 'axios';
import Pagination from 'react-js-pagination';
import './Index.css';

export default class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      eventList: [],
      activePage: 1,
      itemsCountPerPage: 1,
      totalItemsCount: 1,
      pageRangeDisplayed: 10,
    }
    this.handlePageChange=this.handlePageChange.bind(this);
  }


  handlePageChange(pageNumber) {
    axios
    .get('/api/event?page=' + pageNumber)
    .then(({data}) => {
      this.setState({
        eventList: data.data,
        itemsCountPerPage: data.per_page,
        totalItemsCount: data.total,
        activePage: data.current_page,
      })
    });
  }

  componentDidMount() {
    this.handlePageChange(1)
  }

  render() {

    return (
      <div className="container">   
        <h2>一覧</h2>
        <div className="cardGroup">
          <div className="container card-wrap">
            <div className="row">
              {this.state.eventList.map((item) => (<Card
                key={item.id}
                title={item.title}
                imagePath={item.image_path}
                startDate={item.start_date}
                address={item.address}
                />))}
            </div>
            <Pagination
              activePage={this.state.activePage}
              itemsCountPerPage={this.state.itemsCountPerPage}
              totalItemsCount={this.state.totalItemsCount}
              pageRangeDisplayed={this.state.pageRangeDisplayed}
              onChange={this.handlePageChange}
              itemClass='page-item'
              linkClass='page-link'
            />
          </div>
        </div>
      </div>

    );
  }
}

```

##react-js-paginationのパラメーターについて

```react
            <Pagination
              activePage={this.state.activePage}
              itemsCountPerPage={this.state.itemsCountPerPage}
              totalItemsCount={this.state.totalItemsCount}
              pageRangeDisplayed={this.state.pageRangeDisplayed}
              onChange={this.handlePageChange}
              itemClass='page-item'
              linkClass='page-link'
            />
```

| パラメータ名 |  |
|:-:|:-:|
|activePage | 現在表示しているページ番号  |
|itemsCountPerPage | ページあたりのアイテム数(今回は6) |
| pageRangeDisplayed |  ！後述！ |
|  totalItemsCount |アイテムの総数  |
| onChange  | ページ切り替えの時に呼ぶ関数(ページ番号を渡す) |


pageRangeDisplayed：　5の場合
<img width="321" alt="スクリーンショット 2019-01-28 17.14.08.png" src="https://qiita-image-store.s3.amazonaws.com/0/29894/9f023ed9-a75d-fd66-bb31-9f5ecc50f13f.png">


見た目のカスタマイズも可能! bootstrapのcssのclass名を入れています。
```            itemClass='page-item'
              linkClass='page-link'
```



詳細はnpmを見てください
https://www.npmjs.com/package/react-js-pagination


これらをstateとして持たせますので初期値を入れておきます

```react
  constructor(props) {
    super(props);
    this.state = {
      eventList: [],
      activePage: 1,
      itemsCountPerPage: 1,
      totalItemsCount: 1,
      pageRangeDisplayed: 5,
    }
    this.handlePageChange=this.handlePageChange.bind(this);
  }
```



##onChangeから呼び出すhandlePageChangeを作成
axiosを使用しています
ページ番号が引数として渡されるのでAPIのURLに?page=ページ番号のパラメータを付けてリクエストします。返ってきたデータはstateに入れましょう。

```react
  handlePageChange(pageNumber) {
    axios
    .get('/api/event?page=' + pageNumber)
    .then(({data}) => {
      this.setState({
        eventList: data.data,
        itemsCountPerPage: data.per_page,
        totalItemsCount: data.total,
        activePage: data.current_page,
      })
    });
  }
```

ページ切り替えのボタンを押すとstateが更新され再レンダリングされます

```react
            <div className="row">
              {this.state.eventList.map((item) => (<Card
                key={item.id}
                title={item.title}
                imagePath={item.image_path}
                startDate={item.start_date}
                address={item.address}
                />))}
            </div>
```


##マウント時に初期表示するデータを取得する
```react
  componentDidMount() {
    this.handlePageChange(1)
  }
```



##おまけCardコンポーネント作成
```react:Card.js
import React, {Component} from 'react';

export default class Card extends Component {
  constructor(props) {
    super(props);

  }

  render() {
    return (
      <div className="col-lg-4">
        <div className="card mb-4 box-shadow rounded-5">
          <p className="group-badge">イベント</p>
          <img className="card-img w-100 d-block rounded-0" style={{padding: 3}} src={this.props.imagePath}></img>
          <div className="card-body">
            <h4 className="card-title">{this.props.title}</h4>
            <h6 className="text-muted card-subtitle mb-2"></h6>
            <div className="card-detail"></div>
            <div className="card-text">
              <p>
                <i className="fa fa-calendar"></i>
                {this.props.startDate}
              </p>
              <p>
                <i className="fa fa-map-marker"></i>
                {this.props.address}
              </p>
            </div>
            <a className="card-link" href="#">＃仮タグ</a>
            <a className="card-link" href="#">＃仮タグ</a>
          </div>
        </div>
      </div>
    )
  }
}

```
bootstrapStudioで作成しました。タグ多すぎ？cssを弄っているので、サンプル画像と表示が異なると思います。小コンポーネントへの受け渡しの参考までに。。