TanStack Query v5 でサーバー状態の管理を整えたら、コードがかなりスッキリした話

React プロジェクトで TanStack Query(旧 React Query)v5 を導入したところ、非同期データの取得・キャッシュ・ローディング状態の管理が一本化されて、コードがだいぶ整理されました。

🙌 結論から

TanStack Query(旧 React Query)v5 は、サーバーから取得したデータのキャッシュ・ローディング状態・エラー処理をまとめて面倒を見てくれるライブラリです!

useState + useEffect で自前で書いていた非同期処理のロジックをほぼ置き換えられて、コードがかなりスッキリします。

v5 ではオプション周りがシンプルに整理され、以前より書きやすくなっています。

👀 TanStack Query とはなにか

TanStack Query は、React アプリケーションでサーバーとのデータやり取りを管理するためのライブラリです。

「サーバー状態管理」と呼ばれる分野を担当するもので、API からデータを取得してキャッシュし、一定時間後に再取得するといったことを自動でやってくれます(^^)

以前は「React Query」という名前でしたが、Vue や Svelte など React 以外にも対応が進み、「TanStack Query」という名前になりました。

今回は React(TypeScript)での利用を前提に話を進めます。

💡 v5 での主な変更点

v5 の変更で一番感じたのは、オプションの構造がシンプルになった点でした!

v4 では useQuery に複数の引数を渡す書き方でしたが、v5 からはオブジェクト 1 つにまとまりました。

// v4 の書き方
const { data } = useQuery(['posts'], fetchPosts, { staleTime: 60000 });

// v5 の書き方
const { data } = useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  staleTime: 60000,
});

見た目は少し冗長に見えるかもしれませんが、型推論の精度が上がって補完が効きやすくなりました(´・∀・)

また、v5 では onSuccess / onError コールバックが廃止されました。

最初は戸惑いましたが、代わりに useEffectdataerror を監視するほうがシンプルで、コードの意図が伝わりやすくなった気がします。

🔧 実際に使ってみた

私が導入したのは、社内ツールの React プロジェクトです。

もともと useState + useEffect でデータ取得を書いていたのですが、ローディング状態・エラー状態を自前で管理するコードがページごとにあって、少し煩雑な感じがしていました。

TanStack Query を入れてからは、こんなふうに書けるようになりました。

const { data: posts, isLoading, error } = useQuery({
  queryKey: ['posts'],
  queryFn: () => fetch('/api/posts').then(r => r.json()),
});

if (isLoading) return <p>読み込み中...</p>;
if (error) return <p>エラーが発生しました</p>;

ローディングとエラーの分岐が 1 箇所にまとまるだけで、コードの見通しがかなり改善されました(^o^)/

😊 使ってみた感想

導入前は「useState + useEffect で書けているし、わざわざライブラリを増やさなくてもいいか」と思っていました。

ところが、使い始めてみると同じ API を複数のコンポーネントで叩いていたとき、キャッシュが勝手に共有されるのが意外と便利で驚きました!

重複リクエストを気にしなくてよくなっただけで、ページ全体の動作が軽くなった気がします(。•̀ᴗ-)✧

一方で、QueryClient の設定や QueryClientProvider でラップする手順など、導入初期に覚えることはそれなりにあります。

ただ、一度慣れてしまえばドキュメントも充実しているので、困ることは少ないと思います!

🎯 useSuspenseQueryとplaceholderDataも試した

onSuccess / onError の廃止に慣れてきたところで、もう一つv5の変更点で気になっていた useSuspenseQuery も試してみました。

これは React の Suspense と組み合わせて使う専用フックで、isLoading の分岐を自分で書かなくてよくなります。

function PostList() {
  // isLoading の分岐が不要。データは必ず存在する前提で書ける
  const { data: posts } = useSuspenseQuery({
    queryKey: ['posts'],
    queryFn: () => fetch('/api/posts').then(r => r.json()),
  });

  return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}

// 呼び出し側で Suspense を使ってローディングをまとめて表現する
<Suspense fallback={<p>読み込み中...</p>}>
  <PostList />
</Suspense>

コンポーネント側から if (isLoading) の分岐が消えるので、データがある前提のロジックだけに集中できるのがかなりスッキリしました(・∀・)

もう一つハマった点として、v5では keepPreviousData(ページネーションなどで前のデータを表示し続ける機能)が廃止されていて、代わりに placeholderData に専用の関数を渡す形に変わっています。

import { keepPreviousData } from '@tanstack/react-query';

const { data } = useQuery({
  queryKey: ['posts', page],
  queryFn: () => fetchPosts(page),
  placeholderData: keepPreviousData,
});

keepPreviousData という同名の関数が別のヘルパーとしてエクスポートされているので、最初「あれ、廃止されたはずじゃ」と混乱したんですが、placeholderData に渡す関数の名前として生き残っていた、というオチでした(^^;)

useSuspenseQuery には placeholderData が効かない(グローバル設定からは引き継がれる)という癖もあるので、Suspenseとページネーションを両方使いたいときは少し設計を考える必要がありそうです。

パパ系Webエンジニア〜愛用ガジェット一覧 🛍️

この記事を書いている作業環境のガジェットを楽天ROOMにまとめています ✨ 在宅エンジニアのデスク作りの参考にどうぞ 🙌

🙌 まとめ

  • TanStack Query v5 はサーバーデータのキャッシュ・ローディング・エラーを一括管理できるライブラリ!
  • v5 からオプションがオブジェクト 1 つにまとまり、型推論がより精度よく働くようになった
  • 同じ API を複数箇所で叩いても、キャッシュが自動で共有されるのが意外と便利です!
  • useState + useEffect の自前実装を置き換えるだけでコードがかなりスッキリする

React でサーバーとのデータやり取りに困り感を覚えている方は、ぜひ一度試してみてください🙌

※ TanStack QueryのAPIは今も更新されています。最新情報は 公式ドキュメント をご確認ください👀