【状態管理】React Queryのキャッシュを使ってAPI通信を最小限に抑えたPaginationを実装した

September 03, 2022
React

前回は「【状態管理】React QueryとReact Hooksを組み合わせる」を書きました。

今回は、React QueryでAPIレスポンスをキャッシュして、API通信を最小限に抑えたPaginationを実装しました。

React Queryの紹介

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

demo

https://react-playground-one-theta.vercel.app/

ポイント

  • 各ページのデータ(APIレスポンス)はReact Queryでキャッシュされます。そのため、前のページに移動する際は、キャッシュから取得したデータを瞬時に表示します。同時に、一度目にフェッチしてから30秒以上経過していた場合は、バックグラウンドで再フェッチを行いフレッシュなデータに差し替えてキャッシュに保存します。
  • 各ページを表示時に、次のページのデータもフェッチします。そのため、次のページを表示する際はキャッシュから取得したデータを瞬時に表示することができます。
  • リロードするとキャッシュは削除されます。

Paginationを実装する方法は色々ありますが、どのタイミングでどれ位の量のデータをフェッチするかで、UI/UXが大きく変わります。 例えば、

  1. 初期表示時に全データをフェッチし、ClientでPaginationする。->データ量が多くなると初期表示が重くなる。
  2. 初期表示は1ページ分のみフェッチし、「次へ」「戻る」を押下したタイミングで必要な量のデータをAPIでフェッチし、Clientで表示する。->ページングの度にloadingが発生するのでUXが悪化する。

React Queryを使うと、上記の問題を簡単に回避できます。

github repo

https://github.com/chanfuku/react-playground

実装ポイント1

// ...省略

const queryOptions = {
  // 次のページのデータがフェッチできるまで、今のページを表示する
  keepPreviousData: true,
  // データの賞味期限が切れるまでの時間(ms)
  staleTime: 30 * 1000,
}

async function fetchProjects(page = 0) {
  const { data } = await axios.get('/api/projects?page=' + page)
  return data
}

function Example() {
  const queryClient = useQueryClient()
  const [page, setPage] = React.useState(1)

  const { status, data, error, isFetching, isPreviousData } = useQuery(
    ['projects', page],
    () => fetchProjects(page),
    queryOptions,
  )
  // ...省略

useQuery()の第一引数にあたる部分、つまり、['projects', page]がキャッシュのkeyになる部分です。

() => fetchProjects(page)で取得したAPIレスポンスを['projects', page]というキーでキャッシュに保存しています。

そして、queryOptionsというオブジェクトのstaleTimeオプションで、データの賞味期限が切れるまでの時間等も設定できます。

データの賞味期限内の場合は再度そのデータにアクセスすると、キャッシュから返却されますが、賞味期限切れの場合はまずキャッシュを返却しつつ、APIから新鮮なデータを取得し、既存のデータを書き換えて再度キャッシュに保存します。

このstaleTimeはどの位の時間を設定すべきか、が結構悩みどころになりそうですが、そのアプリケーションがどれだけリアルタイムなデータを必要としているかで判断が分かれそうです。

特にリアルタイム性を求めない場合は、staleTime: Infinityにすれば、再フェッチされなくなります。

ちなみに、microCMSさんではInfinityに設定しているようです。

ReactQueryでキャッシュを最大限利用する

実装ポイント2

  // Prefetch the next page!
  React.useEffect(() => {
    if (data?.hasMore) {
      queryClient.prefetchQuery(
        ['projects', page + 1],
        () => fetchProjects(page + 1),
        queryOptions,
      )
    }
  }, [data, page, queryClient])

queryClient.prefetchQueryを使うと、表示しているページの次のページのデータを事前にAPIからフェッチしてキャッシュに保存しておくことができます。 これにより、「次へ」ボタンを押した時に瞬時に次のページを表示することが出来ます。

感想

React Queryはサーバーサイドとクライアントサイドの両方の状態を出来るだけ同期させたい、けれどもAPI通信が増えるとUI/UX悪化するからbackgroundで同期しますよ。という、VuexやReduxとは全く異なるコンセプトから生まれたライブラリのようです。

ちなみに、vue-queryはこちらにありますが、TanStack Query公式ではcomming soonと記載されています。

個人的には、もし今後業務で状態管理ライブラリを選定出来るチャンスがあれば、是非react query(TanStack Query)を推したいと思います。


Profile picture

React, Vue, TypeScript, Node.js, PHP, Laravel, AWS, Firebase, Docker, GitHub Actions, etc...