TanStack Query でサーバー状態管理を任せたら、データ取得コードがかなりシンプルになった話

React プロジェクトのデータ取得に TanStack Query(旧 React Query)を導入したところ、useEffect + fetch の手書きコードがほぼなくなり、ローディングやキャッシュ管理がかなり楽になりました。

🙌 結論から

React プロジェクトのデータ取得部分に TanStack Query を導入したら、useEffect + fetch で手書きしていたローディング・エラー・キャッシュのコードがほぼなくなりました!

サーバーから取ってくるデータ(サーバー状態)の管理は、useState や useReducer で手書きするより TanStack Query に任せてしまうほうがかなり楽です。

もっと早く使い始めておけばよかったと思っています(^^;)

💡 TanStack Query とはなにか

TanStack Query(旧 React Query)は、React アプリのサーバー状態管理ライブラリです。

「サーバー状態」というのは、APIから取得したデータのことです。

ローカルのUIの状態(モーダルの開閉など)と違い、サーバー状態は「最新のデータに保つ」「再フェッチのタイミングを制御する」「キャッシュする」といった管理が必要になります。

これを useState で手書きすると、コードがどんどん増えていく…という経験、けっこうしてきました(´・ω・`)

TanStack Query はその辺をまるごと引き受けてくれます!

👀 基本的な使い方

セットアップはシンプルです。

npm install @tanstack/react-query

アプリのルートで QueryClientProvider を設定します。

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

export default function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <MyApp />
    </QueryClientProvider>
  );
}

あとはデータを取得したいコンポーネントで useQuery を使うだけです。

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

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

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

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

isLoadingerrordata が自動で管理されるので、useState で手書きしていた頃と比べると記述量がかなり減ります!

🛠 便利だった機能

実際に使ってみてよかった機能をいくつか紹介します。

キャッシュの自動管理

同じ queryKey に対するデータはキャッシュされ、ウィンドウにフォーカスが戻ったときに自動で再フェッチされます。

バックグラウンドで最新データを取得してくれるので、ユーザーが常に新しいデータを見られる状態になります(^_^)

更新処理(useMutation)

データの送信・更新には useMutation を使います。

const mutation = useMutation({
  mutationFn: (newPost) => fetch('/api/posts', {
    method: 'POST',
    body: JSON.stringify(newPost),
  }),
  onSuccess: () => {
    queryClient.invalidateQueries({ queryKey: ['posts'] });
  },
});

onSuccessinvalidateQueries を呼ぶと、投稿後に一覧を自動で再フェッチしてくれます!

ページネーション・無限スクロール

useInfiniteQuery を使えば無限スクロールもシンプルに実装できます。

ここまでの機能が1つのライブラリに揃っているのが、相当ありがたいです(^o^)

✨ 使ってみた感想

導入して最初に感じたのは、コンポーネントがすっきりしたことです。

以前は useState でローディング・エラー・データの3つを管理して、useEffect でフェッチして…という書き方をしていました。

それが useQuery 1つで済むようになったので、コードが追いやすくなりました!

queryKey の設計だけは最初に少し迷いました。配列でネストすることでキャッシュのスコープを分けられる仕組みなので、規模が大きくなる前に決めておくと後が楽です(・_・;)

あと、TypeScript との相性もかなり良くて、型推論がしっかり働くのも気に入っています!

🚀 prefetchQueryとselectで体感速度を上げる

基本の useQuery に慣れてきたところで、prefetchQuery を使った先読みも試してみました。

「一覧ページから詳細ページに遷移する」という導線で、一覧のホバー時にあらかじめ詳細データを取りにいくようにしています。

function PostListItem({ id }: { id: number }) {
  const queryClient = useQueryClient();

  return (
    <li
      onMouseEnter={() => {
        queryClient.prefetchQuery({
          queryKey: ['post', id],
          queryFn: () => fetch(`/api/posts/${id}`).then(r => r.json()),
        });
      }}
    >
      <Link href={`/posts/${id}`}>記事を読む</Link>
    </li>
  );
}

ホバーした時点でキャッシュに詳細データが乗るので、実際にクリックして遷移したときにはすでにデータが揃っている状態になります(・∀・)

体感の速度がかなり変わって、「クリックしてから読み込み中がチラつく」というストレスがなくなりました。

もう一つ使うようになったのが select オプションです。

APIレスポンスが大きいのに、コンポーネントが必要としているのはごく一部のフィールドだけ、というケースはよくあります。

const { data: postTitle } = useQuery({
  queryKey: ['post', id],
  queryFn: () => fetch(`/api/posts/${id}`).then(r => r.json()),
  select: (post) => post.title,
});

select を挟んでおくと、コンポーネントが受け取るのは変換後の値だけになるので、不要なプロパティが変わっても再レンダリングされにくくなるという副次的なメリットもあります。

「先読みで待ち時間を減らす」「selectで無駄な再レンダリングを減らす」の2つを組み合わせてから、一覧→詳細の遷移がかなり軽快になりました!

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

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

👍 まとめ

TanStack Query は、React アプリのサーバー状態管理をごっそり任せられるライブラリです。

useEffect + fetch の手書きコードが減り、ローディング・エラー・キャッシュ管理が自動化されます。

個人開発から仕事のプロジェクトまで幅広く使えて、導入コストも低いのがうれしいですね!

まだ使ったことがない方は、一度試してみてください(^o^)/✨

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