🐈

RTK-Queryで無限ローディングを実装してみた

2022/03/20に公開

はじめに

RTK-Queryは、無限ローディングの機能がサポートされてないようで、どうやって実装するのか気になったので試したことをまとめました。

https://github.com/reduxjs/redux-toolkit/issues/1159

https://github.com/reduxjs/redux-toolkit/discussions/1171

https://github.com/reduxjs/redux-toolkit/discussions/1163#discussioncomment-855577

https://stackoverflow.com/questions/67909356/is-there-any-way-to-fetch-all-the-responses-stored-in-api-slice-rtk-query

他のデータフェッチライブラリとの比較

react-queryが出してる比較表をみると、RTK-Query以外は対応していました。(Infinite Queries)

https://react-query.tanstack.com/comparison

SWR

https://swr.vercel.app/docs/pagination#infinite-loading

React Query

https://react-query.tanstack.com/guides/infinite-queries

作ったもの

Cats API を使って、無限ローディング、無限スクロールを実装してみました。

サンプルサイト

https://rtkquery-infinite.vercel.app/

ライブラリのバージョン

"@reduxjs/toolkit": "^1.8.0",
"react-redux": "^7.2.6",
"redux": "^4.1.2",

サンプルコード

https://github.com/shimabukuromeg/rtkquery-infinite

Cats API

https://docs.thecatapi.com/

実装の内容

データの取得

まず、useListCatsQuery にて、現在の表示データと少し余分なデータ(次のページのデータ)を事前に取得します。infinite 関数は、現在のページにとそれ以前に取得した各ページのCatApiの結果を集約する関数です。

// 現在のページ。画面遷移した際にも値を保持していたいのでどこまで読み込んだかのステートはReduxでグローバルに保持
const currentPage = useSelector((state: RootState) => state.currentPage.value)
// 現在のページのCatApiを叩いた結果を取得
useListCatsQuery({ page: currentPage })
// 次のページのCatApiを叩いた結果を取得(すぐ表示できるようにするため)
useListCatsQuery({ page: currentPage + 1 })
// これまでに取得した各ページのCatApiの叩いた結果を集約する関数の実行
const result = infinite(currentPage)

https://github.com/shimabukuromeg/rtkquery-infinite/blob/main/src/pages/index.tsx#L14-L16

infinite関数の詳細は、現在のページ数分の要素を持つ配列を作成しループを回し、キャッシュされたCatApiのデータを集約するようにしています。

export const infinite = (currentPage: number) => {
  return [...Array(currentPage)].map((_, i) => i + 1).map((page) => {
    return api.endpoints.listCats.select({ page })(store.getState()).data
  }).flat();
}

https://github.com/shimabukuromeg/rtkquery-infinite/blob/main/src/services/cats.ts#L32-L36

api.endpoints.listCats.select あたりで、RTK-Queryで取得したキャッシュされたデータへのアクセスしています。

キャッシュされたデータへのアクセスの詳細は以下のドキュメントが参考になります。

https://redux-toolkit.js.org/rtk-query/usage/usage-without-react-hooks

Accessing cache data and request status information can be performed using the select function property of a query endpoint to create a selector and call that with the Redux state. This provides a snapshot of the cache data and request status information at the time it is called.

データの表示

表示側では、infinite(currentPage) で取得した result を表示するようにします。

        {
          result?.map((cat, index) => (
            <Flex key={cat?.id} w={400} flexDirection="column" alignItems="center">
              <Text>{`${index + 1}: ${cat?.id}`}</Text>
              <Image alt={cat?.id} src={cat?.url} width={400} />
            </Flex>
          ))
        }

https://github.com/shimabukuromeg/rtkquery-infinite/blob/main/src/pages/index.tsx#L26-L33

Load Moreボタンをクリックすると、currentPageがインクリメントされて、resultinfinite(currentPage) で取得したresult)の値が更新され、追加されたページのCatApiの取得結果が追加されます。(Load Lessボタン押したらcurrentPageがdecrementされる逆の処理が行われます。)

<Button
    onClick={() => {
        dispatch(increment())
        }}
    colorScheme='teal' size='lg'
>
    Load More
</Button>

https://github.com/shimabukuromeg/rtkquery-infinite/blob/main/src/pages/index.tsx#L35-L41

おわりに

SWRはこれまでに使ったことがあって、useSWRInfinite という無限ローディングを実装するための便利な機能があることを知っていたのですが、RTK-Queryだと機能としては提供されてなかったので実装方法について気になっていて、今回試せてよかったです。

RTK-Queryの場合は無限ローディングの機能は提供されていないものの、データフェッチした値のキャッシュはReduxのstoreに保持されているので、カスタムフックを使わずにセレクターを利用して取得することができ、今回のような実装で無限ローディングを実装できました。(思っていたよりもシンプルに実装できた)

RTK-Queryの無限スクロールについては、いろいろとディスカションしてる様子がみれて勉強になったので、今後もウォッチしておこうと思いました。

GitHubで編集を提案

Discussion