はじめに
React QueryのuseInfiniteQuery()で無限スクロールを実装したので記事にまとめました。
「React Queryって何?」という方は、よろしければ前回の記事もご覧ください。
useInfiniteQueryとは
useInfiniteQuery()はデータの一部で返される次のクエリを追跡します。
useQuery()とは返すdataの中身が異なり、pagesとpageParamsの2つのプロパティを含んだオブジェクトとなります。
pagesはクエリの結果を配列で返し、pageParamsは実行されたクエリのキーを配列で返します。
上がpages、下がpageParamsの結果の例となります。

また、useInfiniteQuery()の記述例は以下のようになります。
const {
data,
fetchNextPage,
hasNextPage,
isLoading,
isFetching,
isError,
error,
} = useInfiniteQuery(
'sw-people',
({ pageParam = initialUrl }) => fetchUrl(pageParam),
{
getNextPageParam: (lastPage) => lastPage.next || undefined,
}
);
pageParamには初期値としてdefaultUrlが設定されていますが、optionであるgetNextPageParamによって更新されます。
以上の例では、最新ページのデータlastPageから次のpageParamをlastPage.next || undefinedとして設定していますが、allPagesというパラメータも利用することができます。
このように、pageParamの現在値はReact Queryによって管理されます。
また、useInfiniteQuery()が返すオブジェクトのプロパティには、useQuery()にはなかった以下のようなものが存在します。
-
fetchNextPage: 次ページのデータのフェッチを許可する関数 -
hasNextPage:getNextPageParamによってpageParamが更新されたらtrueを返す(undefinedのときfalse) -
isFetchingNextPage:fetchNextPageで次ページのデータをフェッチしている間trueを返す(loadingのフラグに使用)
これまでに登場したワードを実際のデータ取得フローあてはめると以下のようになります。
- コンポーネントがマウントされて最初のページをフェッチする
-
getNextPageParamがpageParamを更新する -
hasNextPageがtrueであれば、イベント(スクロールやボタンクリック)によってfetchNextPageを実行する -
hasNextPageがundefinedになるまで2→3を繰り返す
実装例
useInfiniteQuery()とReact Infinite Scrollerを使って、無限スクロールを実装します。
import InfiniteScroll from 'react-infinite-scroller';
import { useInfiniteQuery } from 'react-query';
import { Person } from './Person';
const initialUrl = 'https://swapi.dev/api/people/';
const fetchUrl = async (url) => {
const response = await fetch(url);
return response.json();
};
export function InfinitePeople() {
const {
data,
fetchNextPage,
hasNextPage,
isLoading,
isFetching,
isError,
error,
} = useInfiniteQuery(
'sw-people',
({ pageParam = initialUrl }) => fetchUrl(pageParam),
{
getNextPageParam: (lastPage) => lastPage.next || undefined,
}
);
if (isLoading) return <div className="loading">Loading...</div>;
if (isError) return <div>Error! {error.toString()}</div>;
return (
<>
{isFetching && <div className="loading">Loading...</div>}
<InfiniteScroll loadMore={fetchNextPage} hasMore={hasNextPage}>
{data.pages.map((pageData) => {
return pageData.results.map((person) => {
return (
<Person
key={person.name}
name={person.name}
hairColor={person.hair_color}
eyeColor={person.eye_color}
/>
);
});
})}
</InfiniteScroll>
</>
);
}
react-infinite-scrollからInfiniteScrollをインポートして、Personコンポーネントをラップします。
loadMoreプロパティにfetchNextPageを、hasMoreプロパティにhasNextPageを設定することで次ページのデータのフェッチを制御することができるようになります。
参考資料