SWRで「もっとみる」ボタンを実装する
開発環境
- 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がカーソルやオフセットの場合も公式のサンプルが少しありますので、こちらも参考にしてみてください。