🐮

TanStack Query v5で廃止された refetchPage の代替実装

に公開

こんにちは、koyablueです。
最近TanStack Queryのv4 → v5へのアップデート作業を行いました。その際、useInfiniteQueryrefetchPageというオプションが廃止されており、v4までの「ページを選別してリフェッチする」という挙動をどう置き換えるべきか、いろいろと調査が必要になりました。
この記事ではその調査内容と代替実装をまとめます。

useInfiniteQueryとは

TanStack Queryが提供するフックで、無限スクロールや「もっと読み込む」など、ページを追加取得して積み上げていくタイプのUIを実装したい場合に使用されます。

refetchPage

https://tanstack.com/query/v4/docs/framework/react/guides/infinite-queries#refetchpage

useInfiniteQueryが返すrefetchに渡せるオプションです。

どのページを再取得するかを (page, index) => booleanで指定でき、例えばindex === 0とすれば 先頭ページだけを再取得できます。

v4のドキュメントにも先頭ページだけを再取得するサンプルコードが載っており、実運用でも使われがちなパターンだと思います。
私たちの実装でも、同じように先頭ページだけ再取得するというユースケースで利用していました。

v5で廃止

このオプションはv5で廃止されており、公式のマイグレーションガイドにはrefetchPageを廃止してmaxPagesを導入したという記述があります。
ただこのmaxPagesrefetchPageをそのまま代替するものではないようで、ドキュメントを見ると

The maximum number of pages to store in the infinite query data.

When the maximum number of pages is reached, fetching a new page will result in the removal of either the first or last page from the pages array, depending on the specified direction.

と説明されています。つまり、maxPagesはinfinite queryのdataが保持するページ数の上限を設定するためのオプションです。refetchPageの単純な代替というわけではありません。

代替実装

ではv4のようなページ選別リフェッチはv5で不可能になったのかというと、そういうわけではないようです。直接の代替APIはありませんが、workaroundが紹介されているdiscussionを発見しました。
https://github.com/TanStack/query/discussions/5692

const resetInfiniteQueryPagination = (): void => {
 queryClient.setQueryData(queryKey, (oldData) => {
   if (!oldData) return undefined;

   return {
     ...oldData,
     pages: oldData.pages.slice(0, 1),
     pageParams: oldData.pageParams.slice(0, 1),
   };
 });
};
const onRefresh = () => {
 resetInfiniteQueryPagination();
 refetch();
}

このworkaroundは何をしているのか

キャッシュを先頭ページだけに切り詰めています。

pagepageParamuseInfiniteQueryが返すdataの型です

interface InfiniteData<TData, TPageParam = unknown> {
    pages: Array<TData>;
    pageParams: Array<TPageParam>;
}

pagesで各ページのデータの配列を持ち、pageParamsにページパラメータの配列を持ちます。
このpagespageParamsの配列に対して.slice(0, 1)することで先頭だけ残し、queryClient.setQueryDataを使ってキャッシュを更新します。

例えば、3ページ分読み込んでいた場合、
pages: [page1, page2, page3], pageParams: [1, 2, 3]

pages: [page1], pageParams: [1]
のようになるイメージです。

その後、キャッシュに先頭ページだけ残った状態でrefetch()を行うことで、先頭ページだけ再取得するという挙動を実現しています。

実際に使う時はカスタムフックにまとめるとよさそうです。

// 例)
const { postInfiniteQuery, refetchOnlyFirstPage } = usePostsInfiniteQuery();

なぜこのworkaroundが適切と言えるのか

メンテナーが以下のようにコメントしているので。

yes, this is the best way to handle the case.

おわりに

v5.0.0がリリースされたのが2023年なのでかなり今更感がありますが...今後v5にアップデートする方の一助になれば幸いです🙏

Social PLUS Tech Blog

Discussion