4
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React Hooks でページネーションを実装する

Last updated at Posted at 2019-08-21

やりたいこと

Hooks がリリースされてから、コンポーネントのシンプルな状態管理に関してはuseStateなどで済ませられるようになったが、やや複雑なコンポーネントの状態管理+アルファを Hooks でやってみたかった。

つくったもの

今回はuseReducerを使って市町村名をリストで表示するページネーションを実装してみた。
サンプルコード:https://codesandbox.io/s/paginator-demo-mnxvc
UIフレームワークは Material UI でサクッと用意。

useReducer を使った例としては公式のリファレンスでもカウンターなどがありますが、もう少し実用的で、複雑な状態管理と副作用をうまく利用することが要求されるコンポーネントを作って試してみたいと思っていたので今回それをやりました。

ページネーションを題材とした理由は、主に

  • コンポーネントの操作で非同期にデータをGETする必要性がある
  • ページの始点・終点・中間点等でコンポーネント(「進む」「戻る」などのボタン、ページ位置の表示)の制御を切り替える必要がある
  • 実際のWebアプリケーションでも使用される機会が多い

という点。その他に1ページあたりの表示件数とか、データの表示スタイルの切り替えといったアレンジも加えやすいので、思いつく限り今回は機能を盛り込んでみました。
※今回はサンプルコードを全て公開している都合上、公開APIを叩いて非同期でデータをGETするかわりに静的なデータ(市町村名とIDの一覧)を用意し擬似的に何件かずつarray.sliceで切り出して表示するという実装に変えてあります。

ソースコードとコンポーネントについて

詳しくは上記の CodeSandbox の中身をみていただくとして、今回はページネーションのコンポーネントとReducerの動きについて軽く説明します。

<Paginator />コンポーネント

初期Stateは以下のように設定しています。

index.js
function App() {
  return (
    <div className="App">
      <Container fixed>
        <Paginator
          sum={cities.length} //データの総件数
          per={10} //1ページあたりの表示件数
          initialData={cities.slice(0, 10)} //読み込み時に表示するデータ
          component={ListComponent} //データを渡すPresentaionコンポーネント
        />
      </Container>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

<Button> 前へ・次へ

ページの操作を行います。
「前へ」ボタンがクリックされたら前n件のデータとともにviewPreviewという関数を、「次へ」ボタンがクリックされたらviewNextという関数を発行します。

Paginator.js
const reducer = (state, action) => {
  switch (action.type) {
    //[前へ]ボタンをクリック時に発行。
    //ページ数のデクリメント
    case "viewPreview":
      return {
        ...state,
        currentPage: state.currentPage - 1,
        resourceData: action.data
      };
    //[次へ]ボタンをクリック時に発行。
    //ページ数のデクリメント
    case "viewNext":
      return {
        ...state,
        currentPage: state.currentPage + 1,
        resourceData: action.data
      };
    .
    .
    .
  }
};

reducer は発行される直前のstatedispatch()の中身(≒変更するstate)をactionとして引数にとります。変更を加えないstateは...stateとスプレッド演算子を使って return しないと上手くコンポーネントが機能しないため忘れずに。

<Select>1ページあたりの表示件数の切り替え

Selectボックスを変更した場合、setPerAmountという関数を発行します。

Paginator.js
//reducer
    //表示件数の切り替え時に発行。
    //現在のページを1ページ目にリセット。
    case "setPerAmount":
      return {
        ...state,
        currentPage: 1,
        per: action.per,
        pageAmount: Math.ceil(state.sum / action.per),
        resourceData: action.data
      };

表示件数を切り替えた場合は一番先頭のデータからまた表示し直すことが良い?気がするのでactionに入っているデータは先頭からn件の内容です。

<Switch>表示形式(コンポーネントのスタイル)を切り替え

初期状態では市町村名とIDが2段組になったリスト形式で表示していますが、スイッチを切り替えることでシンプルな1行のコンポーネントで現在のページの内容を表示し直します。
スイッチの切り替え時にデータを1行ずつ表示するための子コンポーネントとswitchComponentという関数を発行します。

Paginator.js
//reducer
    //表示形式の切り替え時に発行。
    case "switchComponent":
      return {
        ...state,
        component: action.component
      };

最後に

公式リファレンスを読むと使い方がなんとなくわかった気になりますが、実務ではカウンターやTodoリスト以上に複雑な機能を扱うことの方が多いためどういう場面で Hooks の力を最大限利用できるのかはまだ手探り中です。今回はuseContext抜きで実装しましたが、扱うコンポーネントの数が増えてきたら避けては通れなくなるかも。

4
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?