📝

【React修行日記】TanStack QueryでAPIデータを取得

に公開

学習の目的

  • ReactでAPIからデータを取得する方法を理解する
  • TanStack Queryを使ったデータ取得・状態管理の基本を学ぶ

ReactでAPIからデータを取得する方法

ReactでAPIからデータを取得する方法は大きく分けて2つの方法がある。

①useEffect + fetch/axios

useEffectを使用してデータをフェッチすることも可能ではあるが、この方法はReact公式でも推奨されてない
https://ja.react.dev/reference/react/useEffect#what-are-good-alternatives-to-data-fetching-in-effects

②サードパーティライブラリを使う方法

AxiosやSWR, TanStack Queryなどがある。
データ取得だけでなくキャッシュ管理、リトライ、再取得、状態管理などが手軽に実装可能。

今回は、その中でもTanStack Queryを使ってAPIからデータを取得してみる!

TanStack Queryとは

TanStack Query(旧 React Query)は、Reactで非同期データを安全に管理するためのライブラリ。
https://tanstack.com/query/latest
APIのデータ取得そのもの(fetchやaxios)を行うわけではなく、取得したデータのキャッシュ管理・状態管理・再取得などを簡単にしてくれる。
useQueryuseMutationなどのフックを通じて操作する。

特徴

  • キャッシュ管理が自動
    同じAPIにアクセスする際に、前回取得したデータを即座に表示できる
  • 再取得・リトライが簡単
    タブ切り替えやネットワーク復帰時に自動で最新データを取得可能
  • ローディング・エラー管理が簡単
    初回ロード (isLoading) と再取得 (isFetching) などを状態で判別可能

データフェッチライブラリではない

  • TanStack Query自体はfetchやaxiosの代わりにはならない
  • APIリクエストは自分で書く必要がある(fetch / axios など)
  • TanStack Queryは「取得したデータの管理をラクにするライブラリ」という位置付け

TanStack Queryを使ってランダムに犬の画像を取得する

Dog APIを使用して、ボタンを押下することでランダムに犬の画像を表示するアプリを作成してみた...!

準備

①npmでインストール

npm install @tanstack/react-query

②QueryClientとProviderの設定

// App.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Example />
    </QueryClientProvider>
  );
}

export default App;

TanStack Queryを使うには、アプリ全体でQueryClientを用意し、QueryClientProviderでラップする必要がある。

APIフェッチ関数を作成

async function fetchRandomDog() {
  const res = await fetch("https://dog.ceo/api/breeds/image/random");
  if (!res.ok) {
    throw new Error("Fetch failed");
  }
  return res.json();
}

TanStack Query自体はデータ取得を行わないため、fetchやaxiosなどで自分で取得処理を書く必要がある。今回はfetchを使用。

useQuery でデータ取得

const { data, isPending, isError, error, refetch, isFetching } = useQuery({
  queryKey: ["fetchRandomDogs"],
  queryFn: fetchRandomDog,
  refetchOnWindowFocus: false,
});

// データを再取得する関数
const handleRefetch = () => {
  refetch();
};

useQueryの戻り値(今回使用したもの)

  • data
    • API から取得したデータが入る
  • isPending
    • v5から導入されたフラグ
    • 初回のリクエスト中だけtrueになる
  • isError
    • エラーが発生した場合にtrueになる
  • error
    • 実際のエラーオブジェクト
  • refetch
    • 手動で再取得したいときに呼び出す関数
  • isFetching
    • 再取得中(refetchやバックグラウンドフェッチ)にtrue
    • 初回ロード時ではなく「再リクエスト中」に使うのがメイン

オプション(主要なもの)

  • queryKey(必須)
    • データを識別するためのキー
    • 単純な文字列だけでなく、変数やオブジェクトを含めて柔軟に指定可能
  • queryFn(必須)
    • データを実際に取得する関数
    • fetchaxiosなどでAPIにアクセスする処理を書く
    • queryKeyの情報を受け取ることもでき、条件に応じたデータ取得が可能
  • staleTime
    • データを「古い」とみなすまでの時間(ms)
    • デフォルトは 0(常に古い扱い → 画面戻るたびに再フェッチ)
    • 例: staleTime: 1000 * 60 → 1分間は再フェッチしない
  • cacheTime
    • 使われなくなったデータをキャッシュに保持する時間(ms)
    • デフォルトは 5分
  • refetchOnWindowFocus
    • タブに戻った時に自動で再フェッチする挙動
    • デフォルトはtrue

▼その他の戻り値とオプション
https://tanstack.com/query/latest/docs/framework/react/reference/useQuery

実際の表示部分

<div className="flex flex-col gap-8 items-center ">
  <div className="flex items-center justify-center w-[300px] h-[300px]">
    {isError ? (
      <p className="text-red-500">{error.message}</p>
    ) : isPending || isFetching ? (
      <p>🐕 画像を取得中...</p>
    ) : data ? (
      <img
        src={data.message}
        alt="ランダムな犬の画像"
        width={300}
        height={300}
        className="w-full h-full object-contain"
      />
    ) : null}
  </div>

  <button onClick={handleRefetch} disabled={isFetching}>
    別の画像を取得する
  </button>
</div>

ポイント①:条件分岐でエラー時とローディング時の表示を切り替える

{isError ? (
  <p className="text-red-500">{error.message}</p>
) : isPending || isFetching ? (
  <p>🐕 画像を取得中...</p>
) : data ? (
  <img
    src={data.message}
    alt="ランダムな犬の画像"
    width={300}
    height={300}
    className="w-full h-full object-contain"
  />
) : null}

初回レンダリング時にはisPendingを使って「画像を取得中…」を表示し、ボタンで別の画像を取得する場合はisFetchingによってローディング表示がされる。
エラーメッセージはisErrorで条件分岐しているが、本来isFetchingの処理の後に評価されるため、優先度を高めるために先にエラー判定を行う。
ちなみにエラーメッセージ自体はTanStack Queryのerrorを使用して、
{error.message}でfetch関数内でthrowした内容を取得可能。

ポイント②:ボタンで再取得

const handleRefetch = () => {
  refetch();
};
:
:
<button onClick={handleRefetch} disabled={isFetching}>
  別の画像を取得する
</button>

ボタンをクリックした時にTanStack Queryのrefetch()を呼び出してデータを再取得。
通信中はisFetchingを使ってボタンを非活性にすることでユーザーが連打できないように制御。

まとめ

  • Reactではサードパーティライブラリを使うことでデータの取得が手軽にできる
  • TanStack Queryを使うと、データ取得だけでなくローディング・エラー表示や再取得も簡単に扱える

参考

https://tanstack.com/query/latest
https://qiita.com/A-Yuki28/items/1224e19c86bbcd4d4890

Discussion