ReactとRubyで実装する似非ページ番号つきGraphQLページネーション

GraphQLのページネーション実装簡単じゃん！って思ったのですが、ページ番号取得をデフォルトでしてくれないので、色々試行錯誤した結果、GraphQLのページ番号付き（似非）ページネーションがなんとなく実装できたので備忘録的に記録します。

タイトルにもある通りページ番号は似非です。
ページ番号を指定してそれを見にいくことはできませんが、今自分がどこを見ているか表示させます。

####使ったもの
- graphql: 14.5.8
- React: 16.12.0（Hooks使います）
- Ruby: 2.6.2
- Rails: 5.2.3
- material-ui/core": 4.7.1

####できること
- ページネーションでページ番号を表示（正確には何件目〜何件目の表示）
- 全件数の表示

![Screen Shot 2019-12-17 at 12.25.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/312001/7bef98a8-ddd2-3f34-6aa9-9e4261839693.png)

こちらMaterial-UIの[ドキュメント](https://material-ui.com/components/tables/)から取ってきたものですが、右下の方を見ていただくと`1-5 of 13`と表示されていますね。13件の内の1-5件目を表示してるってことですね。これを実装します。

####できないこと
- 指定したページ番号への遷移
- ページ内行数の指定

上の画像からも分かる通り、ページ番号で遷移みたいなのは実装しません（やり方あまり分かってません）。
後、ページ内行数の指定（1ページあたり何件表示させるか）もしません。（こちらは今回自分では不要だったので実装しませんでした。）


##実装
###GraphQL側
まずはGraphQLのクエリを書きます。

```ts:notification.ts
import gql from 'graphql-tag';

export const getDesserts = gql`
  query desserts(
    $first: Int
    $after: String
    $last: Int
    $before: String
  ) {
    desserts(
      first: $first
      after: $after
      last: $last
      before: $before
    ) {
      totalCount
      edges {
        cursor
        node {
          id
          calorie
          fat
          carb
          protein
        }
      }
      pageInfo {
        endCursor
        hasNextPage
        startCursor
        hasPreviousPage
      }
    }
  }
`;

```
cursorとかこの辺の説明は調べれば色々と出てくるので割愛します。
通常のGraphQLのページネーションの実装だと、edgesとpageInfoが返ってきますが、さらにtotalCountと言う項目があります。こちら通常だと定義されていないので、これがちゃんと値を持って返ってくる様に後ほど定義します。

今は定義していないので、このままクエリを投げると以下の様なエラーが返ってきます。なぜなら定義していないから！

```json:error_message
{
  "errors": [
    {
      "message": "Field 'totalCount' doesn't exist on type 'DessertsConnection'",
    }
  ]
}
```

次にカスタムコネクションを実装します。
コネクションがGraphQLでページネーションを実装するための機能（cursorやpageInfo等）を提供しています。
このコネクションにレコードの全件数も返すように`total_count`を加えます。

```ruby:app/graphql/types/desserts_connection.rb

class DessertsEdgeType < GraphQL::Types::Relay::BaseEdge
  node_type(Types::DessertType)
end

class Types::DessertsConnection < GraphQL::Types::Relay::BaseConnection
  field :total_count, Integer, null: false
  def total_count
    object.nodes.size
  end
  edge_type(DessertsEdgeType)
end
```
クエリタイプは以下の様に実装します。

```ruby:app/graphql/types/query_type.rb
module Types
  class QueryType < Types::BaseObject

    field :public_notifications, Types::DessertsConnection, null: false do
      description 'A list of all desserts'
      argument :first, Int, required: false # ページに表示するレコード数（nextで来た時）
      argument :after, String, required: false # レコードにつくID。このIDの直後のレコードからfirst分の件数を表示する
      argument :last, Int, required: false # ページに表示するレコード数（prevで来た時）
      argument :before, String, required: false # レコードにつくID。このIDの直前のレコードからlast分の件数を表示する
    end
    def public_notifications()
      Dessert.all
    end
  end
end
```
これで例えば、`first:5, after:'MQ32'`の様な引数を渡すと、IDがMQ32のレコードの直後のレコードから5件表示してくれます。（このIDはGraphQLが勝手につけてくれるやつです。）

###フロントエンド側
テーブル全部記載すると分量増えちゃうので、ページネーションに関係するところだけ抜粋します。


```jsx:DessertsComponent.jsx

// 親コンポーネント
export const DessertsParent = props => {
   const [queryVariables, setQueryVariables] = useState({
    first: 10,
  });

  const [page, setPage] = useState(0);

  // デザートのデータを取ってくるクエリ
  const { data } = useDessertsQuery({
    variables: { ...queryVariables },
  });
  const desserts = data;

  // クエリに使う情報とページ番号の更新を行う関数
  const paginationEventHandler = (recordRangeInfo, newPage) => {
    setQueryVariables({ ...recordRangeInfo });
    setPage(newPage);
  };

  return(
   <>
     <Child
       desserts={desserts} // 表示するレコード
       paginationEventHandler={paginationEventHandler} // ページネーションで使う関数
       page={page} // ページ番号
     />
   </>
   );
}


// 子コンポーネント
import { TablePagination } from '@material-ui/core'

export const DessertsChild = props => {
  const { desserts, paginationEventHandler, page } = props;

  const handleChangePage = (event, newPage) => {
    let directedPage;
    if (page - newPage < 0) {
      // next page
      directedPage = { //　次のページを取得するための情報
        after: desserts
          ? desserts.desserts.pageInfo.endCursor
          : undefined,
        first: 10,
      };
    } else {
      // previous page
      directedPage = { // 前のページを取得するための情報
        before: desserts
          ? desserts.desserts.pageInfo.startCursor
          : undefined,
        last: 10,
      };
    }
    paginationEventHandler(directedPage, newPage);
  };
 return(
   
   <TablePagination
     component="div"
     count={
       desserts ? desserts.desserts.totalCount : 0
     }
     onChangePage={handleChangePage}
     page={page}
     rowsPerPage={10} // 1ページに表示する件数（今回は10で固定）
     rowsPerPageOptions={[10]} // 1ページに表示する件数のオプション（今回は10だけなので10のみ配列で渡す）
        />
 )
```

これで件数と次ページ、前ページをそれぞれ取得することができました。
connectionをいじれば色々な情報が取ってこれるので、試してみたいと思います。（ページ番号やらもここをいじれば取得できる）



##参考文献
https://www.2n.pl/blog/graphql-pagination-in-rails