😺

useSWRInfinite を利用した管理機能向けページネーションで試行錯誤した話

2024/12/31に公開

前提

  • ユーザなどのリソースを管理する機能を実装したい(要は管理者機能)
  • リソースは 100 や 1000 といったオーダーになるため、ページネーションしたい
  • リソースの取得リクエストでは、useSWRInfinite() を利用している

課題/やりたいこと

  • useSWR()useSWRInfinite() では、無限ローディングなど、追加で読み込んでいく処理のサンプルはあるが、戻るボタンの考慮がされているサンプルはない
  • 管理者機能のよくあるデザインでは、2ページ目に進んだ後に、1ページ目に戻るケースがある
  • そのまま useSWRInifinite()setSize() を利用すると、ページを戻った時にもリクエストが送られてしまい、API側に負荷がかかる(そのため、戻る場合には読み込み済みのデータを参照するだけにしたい)
  • サンプルの実装を流用すると、進むボタン押下時に、1ページに表示するリソース数が10の場合に、10個のリソースが返された際に、ボタンの制御がうまくいかない
    • 具体には、isReachingEndfalse になるため、進むボタンは押下できるが、次のデータが返ってこないのに、次のページを表示しようとしてしまう
    • まずデータをロードし、isReachingEndtrue になった後に、取得したデータ数に応じて、次のページを表示するかしないか判断する必要がある

解決策

  • 戻るボタンクリック時には setSize() は実行しない
  • 進むボタンクリック時には データの取得処理とページの移動処理を独立させ、データ数が変更した場合にのみページを移動する

コード抜粋

const { data, size, setSize } = useSWRInfinite(/*管理機能のリソースのAPIに対して実行*/);

// useSWRのサンプルより抜粋
// https://swr.vercel.app/ja/examples/infinite-loading
const isEmpty = data?.[0]?.length === 0;
const isReachingEnd =
    isEmpty || (data && data[data.length - 1]?.length < PAGE_SIZE);

// 次のページボタンクリック時に呼び出される関数
const onClickNextPage = () => {
    if(isReachingEnd) {
      // 最後まで取得済みなら、ページを増やすだけ
      setPageIndex(pageIndex + 1);
    } else {
      // 未取得なら、追加取得を行う
      setSize(size + 1);
      // ここでページを進めると、もし次のページのデータがない場合、エラーになるため、下のuseEffectを用いる
    }
  }

// 次ページをクリックしたときに、データがあれば、ページを進めるための処理
useEffect(() => {
  if (data && data.length > pageIndex) {
    setPageIndex(data.length);
  }
}, [data]); // data にだけ依存させる

// 戻るページボタンクリック時に呼び出される関数
const onClickPreviousPage = () => {
  // 戻るときは、ページの追加取得はしない
  if (pageIndex > 1) {
    setPageIndex(pageIndex - 1);
  }
}
// 戻るボタンと進むボタンの抜粋
<div className="mt-3 flex items-center gap-2">
  <ButtonIcon disabled={pageIndex === 1} onClick={onClickPreviousPage}><PiCaretLeftFill /></ButtonIcon>
  <span>{pageIndex} / {totalPages}</span>
  <ButtonIcon disabled={isReachingEnd} onClick={onClickNextPage}><PiCaretRightFill /></ButtonIcon>
</div>

他にもやりようがあるかもしれないですが、ひとまずこれでいい感じに動かせたので、備忘録的に書いています。
mutateを利用すると、リソースのCRUDを行った際に結果をstateへうまく反映できました。

Discussion