TanStack Query 入門 – React Query で Github のリポジトリ取得 API を実行

React
Reactフロントエンド

この記事では TanStack(TanStack は以前の React Query)の Query の使い方と基本の動きを紹介しています。

色々な機能がありますが、この記事ではまずは基本的な使い方のメモ。

利用するコードベース

以下で作ったものをベースにしています。

主なライブラリのバージョンは以下。

% npm list --depth=0
├── @tanstack/react-query@4.20.4
├── @types/react-dom@18.0.9
├── @types/react@18.0.26
├── @typescript-eslint/eslint-plugin@5.46.0
├── @typescript-eslint/parser@5.46.0
├── @vitejs/plugin-react@3.0.0
├── eslint-config-prettier@8.5.0
├── eslint-plugin-react-hooks@4.6.0
├── eslint-plugin-react@7.31.11
├── eslint@8.29.0
├── prettier-plugin-organize-imports@3.2.1
├── prettier@2.8.1
├── react-dom@18.2.0
├── react@18.2.0
├── typescript@4.9.4
├── vite-tsconfig-paths@4.0.2
└── vite@4.0.0

インストール

以下の実行でOKです。

yarn add @tanstack/react-query

クエリの実行 – useQuery

基本

基本の使い方はハイライト部分の通り。非常にシンプルにサーバ処理の管理(通信中、エラー、取得後の描画)を行うことができます。
isLoading, isError などは vue-apollo などでもおなじみですね。この類のライブラリを使ったことあれば他のライブラリを利用するのもそんな困らない気がします。

ここで面白いと思ったのは、useQuery が提供する機能と、実際の API をコールする処理が別という点です。ライブラリや特定の API 方式に依存しない形にできる点は TanStack ぽさを感じました。

import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { useState } from 'react'
import './App.css'

// 1. client を生成
const queryClient = new QueryClient()

// 2. アプリケーションを QueryClientProvider で囲む
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <GithubSample />
      <ReactQueryDevtools initialIsOpen={true} />
    </QueryClientProvider>
  )
}

// 3. 使用例
function GithubSample() {
  // useQuery
  //   queryKey: 一意なキーを指定する
  //   queryFn: promise を返す関数を指定する
  const { isLoading, isError, data, error, refetch } =
    useQuery<RepositoriesResponse>({
      queryKey: ['orgs'],
      queryFn: () =>
        fetch(
          `https://api.github.com/search/repositories?q=org:tanstack&sort=stars`,
          {
            headers: {
              Authorization: 'Bearer <TOKEN>',
            },
          },
        ).then((res) => res.json()),
    })

  //  ローディング中
  if (isLoading) {
    return <span>Loading...</span>
  }

  // エラー発生時
  if (isError) {
    return <span>Error: {(error as any).message}</span>
  }

  // data は res.json() の結果にアクセスできる
  return (
    <div className="App">
      <table border={1} style={{ borderCollapse: 'collapse' }}>
        <thead>
          <tr>
            <th>No</th>
            <th>name</th>
            <th>description</th>
            <th>createdAt</th>
            <th>star</th>
            <th>url</th>
          </tr>
        </thead>
        <tbody>
          {data &&
            data.items.map((v: any, index: any) => (
              <tr key={index}>
                <td>{index + 1}</td>
                <td>{v.name}</td>
                <td>{v.description}</td>
                <td>{new Date(v.created_at).toLocaleDateString()}</td>
                <td>{v.stargazers_count}</td>
                <td>{v.url}</td>
              </tr>
            ))}
        </tbody>
      </table>
    </div>
  )
}

export default App

実行結果です。ちゃんと取れていますね。素晴らしい。

実行結果 - useQuery で Github のリポジトリを取得
実行結果 – useQuery で Github のリポジトリを取得

data に型を指定するには useQuery<data型> で指定します(正確には data 以外にエラー型など複数の指定が可能です)。data 型を設定する場合、イメージはこんな感じになります。

type Repository = {
  name: string
  description: string
  created_at: string
  stargazers_count: string
  url: string
  ...
}
type RepositoriesResponse = {
  items: Repository[]
  ...
}
...
// これで data の型が RepositoriesResponse | undefined になります
const { isLoading, isError, data, error } = useQuery<RepositoriesResponse>(...)

余談 Github API の実行について

TOKENはこちらの手順で生成できます。
search API についてはこちらなどを見ることでパラメータが確認できます。

クエリ状態の確認: react-query 用 dev tool

Apollo Client DevTools のような、クエリ結果を確認するための dev tool も提供されています。インストールと組み込みも簡単です。

yarn add @tanstack/react-query-devtools
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <GithubSample />
      <ReactQueryDevtools initialIsOpen={true} />
    </QueryClientProvider>
  )
}

画面下にクエリで取得した情報と、キーに紐づく情報が確認できます。

実行結果 - react-query 用 devtool
実行結果 – react-query 用 devtool

パラメータ渡し

実際のサーバ通信処理ではパラメータを指定するケースの方が多いですが、その場合の方法。
useQuery の queryFn に指定する関数には、以下のような情報が渡されます(TanStack では QueryFunctionContext という型)。queryKey は名前の通り、useQuery で指定されている queryKey です。

ここでは、useState で画面から oraganization の値を受け取り、queryKey で指定されている key がこの名前となり、Github からデータ取得をしています。

/**
 * queryFn に相当する部分を外だし
 * queryKey で指定された変数を参照することが可能
 */
function getReposFn(param: QueryFunctionContext) {
  const [_key, { key }] = param.queryKey as [string, { key: string }]
  return fetch(
    `https://api.github.com/search/repositories?q=org:${key}&sort=stars`,
    {
      headers: {
        Authorization: 'Bearer <TOKEN>',
      },
    },
  ).then((res) => res.json())
}

// 3. 使用例
function GithubSample() {
  const [key, setKey] = useState('tanstack')

  // useQuery
  //   queryKey: 一意なキーを指定する
  //   queryFn: promise を返す関数を指定する
  const { isLoading, isError, data, error, refetch } =
    useQuery<RepositoriesResponse>({
      queryKey: ['orgs', { key }],
      queryFn: getReposFn,
    })
  ///

param の値

param (QueryFunctionContext)の値
param (QueryFunctionContext)の値

なので、クエリを実行するときの動的なパラメータも含めて queryKey のキーに指定することもでき、その情報をもとに、実際に情報を取得するクエリを実行することもできます(書き方によっては同じ queryKey で別のパラメータで情報取得をするといったことももちろん可)。

画面側では organization を選べるようにしておきます。

  return (
    <div className="App">
      {/* refetch で query を再実行する。このとき useState で定義した変数が最新の状態でクエリが行われる */}
      <select
        value={key}
        onChange={async (e) => {
          await setKey(e.target.value)
          refetch()
        }}
      >
        <option>tanstack</option>
        <option>vuejs</option>
        <option>facebook</option>
        <option>angular</option>
      </select>
      ...

以下、実行結果です。select で選んだ内容によって、setKey で更新され、その後 refetch で再度 queryFn を実行しています。

実行結果 - select で選んだ organization に応じた一覧を表示
実行結果 – select で選んだ organization に応じた一覧を表示

実行タイミングの制御(手動トリガー)

Disabling/Pausing Queries を参照。
これまでの例だと読み込み時にリクエストが実行されますが、ボタン押下など何らかのユーザ操作等をトリガーに手動実行したい場合は、enabled プロパティを false または式にすることで制御が可能です。

前者は完全に手動で、公式でもバックグラウンドフェッチなど TanStack Query がもつ多くの機能が無効になるようです。後者は最初のトリガーだけ手動で後は自動にするパターンです。

任意のトリガーでクエリを実行

完全に false に倒した場合です。

  const { isInitialLoading, isLoading, isFetching, isError, data, error, refetch } =
    useQuery<RepositoriesResponse>({
      queryKey: ['orgs', { key }],
      queryFn: getReposFn,
      enabled: false,
    })

この場合は画面上では「 Loading… 」の表示のみがでます。ちなみに、enabled: false の場合の各種データの状態は以下の通りで、isLoading が true なので画面に 「Loading… 」がでています。

初期状態
初期状態

refetch() を実行すると、enabled: false でもクエリが実行されます。この場合の状態は以下です。

一覧取得中
一覧取得中

その後、冒頭の通り一覧が表示されます。最終的な状態は以下の通りです。

一覧表示後
一覧表示後

なお、この状態でもう一度 refetch() を行った場合の、実行中の状態は以下になります。

もう一度 refetch を行った場合
もう一度 refetch を行った場合

そのため、公式にあるように、enabled: false の場合で利用する場合は、isInitialLoading は 初めての取得中という判断はできないため、isLoading && isFetching で初回か否かを判断することになります。

任意のトリガーでクエリを有効化

今度はデフォルト false で切り替えられるようにした場合です。

  const [enableQuery, setEnableQuery] = useState(false)  

  const { isInitialLoading, isLoading, isError, data, error, refetch } =
    useQuery<RepositoriesResponse>({
      queryKey: ['orgs', { key }],
      queryFn: getReposFn,
      enabled: enableQuery,
    })

初期状態は当然ですが enabled: false のパターンと同じです。

ボタン押下で enabled を有効にさせると、状態は以下となります(手動 refetch() 時と同じ)。

ボタン押下後、一覧取得中
ボタン押下後、一覧取得中

その後、冒頭の通り一覧が表示されます。最終的な状態は以下の通りです(これも手動時と同じでした)。

一覧表示後
一覧表示後

まとめ

Tanstak Query は実際の取得方法(ライブラリ / 方式)はなんでもよく、それらがうまくラップされた IF が提供されている点でとても便利だなと感じました。

今回はほんの触りですが基本的な使い方は分かったので、今後は TanStack Query が持っているキャッシュなどの機能についてキャッチアップしていけば使っていけそうです。