logo

SWRで「もっとみる」ボタンを実装する

2024-01-03
11 months ago

開発環境

  • next 13.4.10
  • swr 2.2.4

前提

APIが事前に用意されており、ページネーションの機能が実装済みであることが前提です。

本記事ではGithub APIを利用しています。

少し見た目を整えるためにTailwindCSSを利用していますが、本記事の本筋とは関係はありません。

本題

SWRでページネーションを実装するときは、多くの場合はuseSWRを利用すれば問題ないと思います。

しかしながら、「もっとみる」ボタンを押下してデータを追加していくケースや、無限スクロールなどを実装する場合はuseSWRInfiniteを利用することでシンプルに実装できます。

実装例

'use client';

import useSWRInfinite from 'swr/infinite';

const fetcher = (url: string) => fetch(url).then((res) => res.json());
const PAGE_SIZE = 5;

export default function Sample() {
  const { data, size, setSize, isLoading } = useSWRInfinite(
    (index) =>
      `https://api.github.com/repos/reactjs/react-a11y/issues?per_page=${PAGE_SIZE}&page=${
        index + 1
      }`,
    fetcher
  );

  const issues = data ? [].concat(...data) : [];
  const isLoadingMore =
    isLoading || (size > 0 && data && typeof data[size - 1] === 'undefined');
  const isEmpty = data?.[0]?.length === 0;
  const isReachingEnd =
    isEmpty || (data && data[data.length - 1]?.length < PAGE_SIZE);

  return (
    <div className="w-full">
      <div className="flex justify-between gap-2">
        <input
          value="react-a11y"
          placeholder="reactjs/react-a11y"
          disabled
          className="text-field"
        />
      </div>
      <div className="my-8 flex gap-2 items-center">
        showing {size} page(s) of {isLoadingMore ? '...' : issues.length}{' '}
        issue(s)
      </div>
      {isEmpty ? <p>Yay, no issues found.</p> : null}
      {issues.map((issue) => {
        return (
          <p key={issue.id} className="my-2">
            - {issue.title}
          </p>
        );
      })}
      <div className={isReachingEnd ? 'hidden' : 'mt-4 text-center'}>
        <button
          disabled={isLoadingMore || isReachingEnd}
          onClick={() => setSize(size + 1)}
          className="border rounded-lg px-2 py-1 hover:opacity-70"
        >
          {isLoadingMore ? 'loading...' : 'もっとみる'}
        </button>
      </div>
    </div>
  );
}

※上記の実装は公式のサンプルを参考にしています。

「もっとみる」ボタンを押下するたびに5件データが追加され、取得できるデータがなくなれば「もっとみる」ボタンが非表示になるサンプルです。


useSWRとの大きな違いは返り値のdataの構造です。

通常は以下のようなデータ構造ですが、

[
  { name: 'Alice', ... },
  { name: 'Bob', ... },
  { name: 'Cathy', ... },
  ...
]

useSWRInfiniteの場合は以下のように配列の中に配列があります。

[
  [
    { name: 'Alice', ... },
    { name: 'Bob', ... },
    { name: 'Cathy', ... },
    ...
  ],
  [
    { name: 'John', ... },
    { name: 'Paul', ... },
    { name: 'George', ... },
    ...
  ],
  ...
]

jsxの中でconcat()などの処理をしているのは、フェッチしたデータ構造のままだと扱いにくいためです。


「もっとみる」ボタンを実現するためにやっていることはボタンコンポーネントでの以下の処理だけです。

onClick={() => setSize(size + 1)}

これはsizeをインクリメントすることで、ページ(size)分のデータ取得→dataに結果が配列として追加されます。

(※ getKey関数ではデフォルトで第一引数にpageIndex第二引数に previousPageDataを受け取ります。)

  const { data, size, setSize, isLoading } = useSWRInfinite(
    (index) => // getKey関数
      `https://api.github.com/repos/reactjs/react-a11y/issues?per_page=${PAGE_SIZE}&page=${
        index + 1
      }`,
    fetcher
  );

さいごに

APIのクエリが上記のサンプルのように&page=1&page=2などでデータ取得できるならば参考になると思います。

ページネーションAPIがカーソルやオフセットの場合も公式のサンプルが少しありますので、こちらも参考にしてみてください。

参照