TanStack Queryについてまとめてみた

:::note info
viviONグループでは、DLsiteやcomipoなど、二次元コンテンツを世の中に届けるためのサービスを運営しています。
ともに働く仲間を募集していますので、興味のある方は[こちら](#bottom_link)まで。
:::
## 🔰 はじめに
Reactのデータフェッチライブラリ色々あるけど何がいいんだろう🤔と思って調べていた時に、TanStack Query(旧React Query)を勧められたので、備忘も兼ねてまとめてみました！

## 🚀 TanStack Queryとは

- [TanStack Query](https://tanstack.com/query/latest)とは、**データの非同期取得及び更新・状態管理・キャッシュ**の機能を内包しているライブラリ
- **React/Solid/Vue/Svelte**など殆どのフレームワークで使える（React Nativeも対応しているらしい）
- **APIサーバからのレスポンスを状態管理**することができる
- SWRよりもキャッシュの管理に関して細かく設定できる利点がある

## 🔬 実装例

### 📝 データの取得（GET）
`useQuery`というフックを使用することで、データのフェッチを行うことができます。

```tsx
import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'
import { getTodos, postTodo } from '../my-api'

// QueryClientのインスタンスを作成し、QueryClientProviderに渡す（QueryClient自体はキャッシュで使用する）
const queryClient = new QueryClient()
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  )
}
    
function Todos() {
  // useQueryでデータを取得
  const { isLoading, isError, data, error } = useQuery({
    // queryKeyでキーを指定する。キャッシュ管理などに使用する
    queryKey: ['todos'],
    // queryFnでデータ取得用の関数を入れる
    queryFn: async() => {
      const { data } = await axios.get(
        'https//example.api/todos'
      )
      return data
    },
    // staleTimeにキャッシュが古くなるまでの時間を指定できる。デフォルトでは0（即古いとみなす）
    staleTime: 1000,
    // ウィンドウにフォーカスが当たったら再取得なども可能
    refetchOnWindowFocus: true,
  })

  // isLoadingでローディング状態を取得
  if (isLoading) {
    return <LoadingSpinner />
  }

  // isErrorでエラーかを取得
  if (isError) {
    showErrorDialog()
  }

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

render(<App />, document.getElementById('root'))
```

🚨v5だと、Suspense利用時にuseQueryが使用できないので注意。   **`useSuspenseQuery()`**, **`useSuspenseInfiniteQuery()`**, **`useSuspenseQueries()`**
    を必要に応じて使用していく。
    
### 📝 データの更新（POST）
`useMutation`というフックを使用することで、データの更新を行うことができます。
```tsx
import {
  useQuery,
  useMutation,
  useQueryClient,
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'
import { getTodos, postTodo } from '../my-api'

const queryClient = new QueryClient()
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Todos />
    </QueryClientProvider>
  )
}

function Todos() {
  const queryClient = useQueryClient()
  // useQueryでデータを取得
  const query = useQuery({
    queryKey: ['todos'],
    queryFn: async() => {
      const { data } = await axios.get(
        'https//example.api/todos'
      )
      return data
    },
  })
  // useMutationでデータの更新を行なう
  const mutation = useMutation({
    // mutationFnにデータ更新用の関数を入れる
    mutationFn: async({id, title}) => {
      const { data } = await axios.post(
      'https//example.api/todos',
        {
          id,
          title
        }
      )
    },
    // onSuccessで更新成功時の処理を記載することができる
    onSuccess: (result) => {
      // queryClient.setQueryDataを使用することでキャッシュの値を更新することができる
      queryClient.setQueryData(['todos'], prev => [...prev, result])
    }
  })

  return (
    <div>
      <ul>
        {query.data?.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
      <button
        onClick={() => {
          mutation.mutate({
            id: 1,
            title: 'タイトル',
          })
        }}
      >
        Add Todo
      </button>
    </div>
  )
}
render(<App />, document.getElementById('root'))
``` 

## 📈 TanStack Queryを使用することによるメリット

以前とあるプロジェクトにて、SWRを使用してデータフェッチの部分を開発していたのですが、SWRと比較するとキャッシュ部分に関してかなり細かいところまで詰めることができるなと言った印象です。

👍 useQueryのオプションで、`StaleTime`と`CacheTime`を設定することができるため、ページに応じて、適切なキャッシュ時間を細かく設定することができる

👍 更新頻度が高いページ（リアルタイムにデータの更新が必要なものなど）はStaleTimeを0にして、更新頻度が低いページ（CMSで管理しているものなど）やAPIの処理が重いページはStaleTimeを大きめに設定するなど、適切なキャッシュ設定をすることで、サイト全体のパフォーマンスを高めることができる
 
👍 useQueryのオプションが便利
**enabled**
enabledがtrueの時のみクエリを実行する
例えば認証完了時にのみ実行したいなど、特定のstateの状態を監視して実行するか分岐させたい時に使える
```jsx
const { isLoading, isError, data, error } = useQuery({
  queryKey: ['todos'],
  queryFn: getTodos
  // isAuthenticatedがtrueの時のみクエリを実行
  enabled: isAuthenticated
})
```

**select**
selectをつけることで、取得したデータのfilteringをすることができる
```jsx
const {data} = useQuery({
  queryKey: ['todos'],
  queryFn: getTodos,
  // isCheckedがtrueの要素のみ取得している
  select: (data) => data.map((todo) => todo.isChecked)
)
```

## 💬 所感
個人的には、以下のような条件で適宜プロジェクトに合わせて導入するのが望ましいかと思いました！！
プロジェクト自体がミニマムで、データフェッチがメインでキャッシュはそれほど重視しない => SWR
データフェッチに加えて、ページのキャッシュ部分をもっと突き詰めたい => TanStack Query

## 🔗 参考文献
- https://dev.classmethod.jp/articles/resolved-tanstack-query-v5-undefined-type-issue/
- https://zenn.dev/akfm/articles/react-state-scope
- https://zenn.dev/knowledgework/articles/607ec0c9b0408d?redirected=1

# 🌈 一緒に二次元業界を盛り上げていきませんか？
株式会社viviONでは、フロントエンドエンジニアを募集しています。

https://vivion.jp/recruit/

また、フロントエンドエンジニアに限らず、バックエンド・SRE・スマホアプリなど様々なエンジニア職を募集していますので、ぜひ採用情報をご覧ください。