
こんにちは!フロントエンドエンジニアのY.Kです! 前回までにuseQueryとuseMutationを組み合わせた非同期通信と状態更新を最適化する手法についてご紹介しました。まだご覧になっていない方は、まずこちらの記事をご一読ください。
useMutation×useQueryで非同期処理と状態更新を最適化する方法 - KENTEM TechBlog
さて今回は、TanStack Queryの中核を担う 「QueryClient」 にフォーカスし、仕組みの理解とキャッシュ操作による状態更新のテクニックを、具体的なコード例とともにご紹介します。 これを読めば、あなたも TanStack Query を使いこなせること間違いなし!ぜひ最後までご覧ください。
QueryClientとは
これまで2回にわたってTanStack Queryについてご紹介してきましたが、「そもそも状態はどのように管理されているのか?」と疑問に思われた方も多いのではないでしょうか。 実は、これまでの記事にも登場していた QueryClient と QueryClientProvider が、状態管理において非常に重要な役割を担っています。 以下は、すでにご紹介したエントリーポイントの実装例です。
import { createRoot } from 'react-dom/client' import App from './App.tsx' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' const queryClient = new QueryClient() createRoot(document.getElementById('root')!).render( <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> )
このコードでは、QueryClientProvider コンポーネントと QueryClient インスタンスによって、アプリ全体でのデータ取得とキャッシュの管理が可能になっています。それぞれの役割は以下の通りです。
- QueryClient : TanStack Queryの中心的なインスタンスで、フェッチしたデータのキャッシュや状態を保持します。
- QueryClientProvider : ReactのuseContextを使って、QueryClient をアプリ全体に提供する役割を果たします。
この仕組みによって、各コンポーネントは useQuery や useMutation、useQueryClient などのフックを通じて、統一された方法でデータを取得・更新できるようになります。
また、すべてのキャッシュはこの単一の QueryClient インスタンスで管理されるため、新たにインスタンスを生成しても既存のキャッシュにはアクセスできません。
そこで登場するのが useQueryClient というhooksです。これを使うことで、QueryClientProvider から提供されている QueryClient にアクセスでき、既存のキャッシュに安全にアクセスすることが可能になります。

useQueryとキャッシュ
useQuery は、QueryClient に保持されているキャッシュを利用してデータを管理します。仕組みは以下の図のようになっています。
まず、useQuery は渡された queryKey を使ってキャッシュを確認します。
キャッシュが存在すれば、そのデータを即座に返し、画面表示を高速化します。
キャッシュがない場合は queryFn を実行してデータを取得し、その結果を QueryClient のキャッシュに保存します。
キャッシュが更新されると useQuery の data も自動的に更新されます。
この仕組みによって、同じ queryKey を使う複数のコンポーネント間でデータを共有でき、無駄なリクエストを減らしながら表示をスムーズにすることができます。
QueryClientの役に立った機能2選
useQueryClient を使って QueryClient インスタンスにアクセスすることで、前回ご紹介した invalidateQueries 以外にも、キャッシュに対してさまざまな操作が可能になります。 ここでは、個人的に役に立った2つの機能をご紹介します!
queryClient.getQueryData
指定した queryKey に基づいて、キャッシュからデータを取得するための機能です。
この関数単体で使うケースはそれほど多くありませんが、後述する setQueryData と組み合わせることで、キャッシュの取得・書き換え・更新を効率的に行うことができます。
これにより、データの再取得を待たずに表示を即座に更新できるため、ユーザー体験の向上につながります。
const data = queryClient.getQueryData(["data"])
queryClient.setQueryData
指定した queryKey と新しいデータを使って、キャッシュの内容を直接書き換えるための機能です。
データの再取得を待たずにUIを即座に更新したい場合などに活用されます。
前述の getQueryData と組み合わせることで、キャッシュの取得・書き換え・更新という一連の流れを効率的に実現でき、表示の高速化やユーザー体験の向上につながります。ただここで注意しなければならないのは、このsetQueryDataは楽観的更新もしくはサーバーから帰ってきた結果を書き戻す処理のみで使うことが推奨されていて、ローカルの状態管理のために使用することは非推奨とされています。
const data = queryClient.setQueryData(["data"], newData)
これらを使いこなすことでqueryFnを用いずとも、キャッシュのデータを操作することができ、useQueryから帰ってくるdataを更新することができるのです!
実装
それでは、getQueryData と setQueryData を使ってキャッシュを操作し、データを更新する実装を見ていきましょう。今回は CodeSandbox 上で、Next.js の App Router を使ったデモアプリを作成しました。
デモアプリ:https://codesandbox.io/p/devbox/trusting-solomon-65mx3t
このアプリでは、App Router の Route Handler 機能を利用して API をモックしています。Route Handler は、Next.js で API エンドポイントを定義できる仕組みです。さらに今回の実装では useMutation のライフサイクルを活用します。前回はご紹介できませんでしたがuseMutation には onSuccess や onError、mutateFn以外に、onMutate と onSettled というコールバックがあります。これらはuseMutationの中で以下のような順番で発火します。

このonMutateやonSettledのなかで適切にキャッシュを操作することで、通信量を削減することができユーザー体験の向上が望めます。
今回のデモアプリでは、onMutateでキャッシュを取得して楽観的に更新する処理を行います。エラーが発生しなければ、そのままキャッシュを維持します。もしエラーが発生した場合は、onError でキャッシュを元の状態に戻します。
またここで重要なのが context です。onMutate の戻り値は context として保持され、onError・onSettled の最後の引数に渡されます。これにより、onErrorやonSettled で再度キャッシュを取得する必要がなく、効率的に元の状態へ戻せます。今回はonSettledでの処理は割愛していますが、実際の製品ではサーバー側とのデータの同期を取るとより堅牢な実装になるでしょう。
前回は、成功時に onSuccess で GET 通信を行いキャッシュを更新していましたが、今回はキャッシュ操作のみで完結するため、通信量を削減でき、ユーザー体験の向上につながります。
まとめ
これまで全3回にわたって TanStack Query についてご紹介してきました。特に今回の内容は、公式ドキュメントだけでは理解が難しい部分も多く、実際に先輩社員の知見や公式ブログ、GitHubのDiscussionを参考にしながら整理しました。
このシリーズを通じて、キャッシュの仕組みやQueryClientの活用方法、そして楽観的更新によるUX改善といった、実務で役立つポイントを学んでいただけたと思います。TanStack Queryは、ただデータを取得するだけでなく、キャッシュ戦略を柔軟に設計できる強力な状態管理ライブラリです。
ぜひ、TanStack Queryの機能を実際のプロジェクトで試してみてください。そして新たな知見がありましたら是非教えてください!
参考文献
Overview | TanStack Query React Docs
Practical React Query | TkDodo's blog
おわりに
KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp