Open1

TanStack Query v4

yoshinoyoshino

https://react-query-v3.tanstack.com/overview

Reactアプリケーションのサーバー状態のフェッチ、キャッシュ、同期、更新を簡単に行うことができます。

example

 import { QueryClient, QueryClientProvider, useQuery } from 'react-query'
 
 const queryClient = new QueryClient()
 
 export default function App() {
   return (
     <QueryClientProvider client={queryClient}>
       <Example />
     </QueryClientProvider>
   )
 }
 
 function Example() {
   const { isLoading, error, data } = useQuery('repoData', () =>
     fetch('https://api.github.com/repos/tannerlinsley/react-query').then(res =>
       res.json()
     )
   )
 
   if (isLoading) return 'Loading...'
 
   if (error) return 'An error has occurred: ' + error.message
 
   return (
     <div>
       <h1>{data.name}</h1>
       <p>{data.description}</p>
       <strong>👀 {data.subscribers_count}</strong>{' '}
       <strong>{data.stargazers_count}</strong>{' '}
       <strong>🍴 {data.forks_count}</strong>
     </div>
   )
 }

3 core concepts

  • Queries
  • Mutations
  • Query Invalidation
 import {
   useQuery,
   useMutation,
   useQueryClient,
   QueryClient,
   QueryClientProvider,
 } from 'react-query'
 import { getTodos, postTodo } from '../my-api'
 
 // Create a client
 const queryClient = new QueryClient()
 
 function App() {
   return (
     // Provide the client to your App
     <QueryClientProvider client={queryClient}>
       <Todos />
     </QueryClientProvider>
   )
 }
 
 function Todos() {
   // Access the client
   const queryClient = useQueryClient()
 
   // Queries
   const query = useQuery('todos', getTodos)
 
   // Mutations
   const mutation = useMutation(postTodo, {
     onSuccess: () => {
       // Invalidate and refetch
       queryClient.invalidateQueries('todos')
     },
   })
 
   return (
     <div>
       <ul>
         {query.data.map(todo => (
           <li key={todo.id}>{todo.title}</li>
         ))}
       </ul>
 
       <button
         onClick={() => {
           mutation.mutate({
             id: Date.now(),
             title: 'Do Laundry',
           })
         }}
       >
         Add Todo
       </button>
     </div>
   )
 }
 
 render(<App />, document.getElementById('root'))

Guides & Concepts

Important Defaults

Queries

  • 一意のキーに結びついた非同期データソースへの宣言的依存関係
  • サーバーからデータをフェッチすることができます
const result = useQuery('todos', fetchTodoList)

IsLoading
現在フェッチ中

IsError
エラーが発生した

IsSuccess
クエリが成功し、dataが利用可能

IsIdle
現在無効になっている

error
クエリがisError状態の場合、errorプロパティでエラー確認可能

data
クエリが成功状態の場合、dataプロパティでデータ取得可能

isFetching
どのような状態でも、バックグラウンドリフェッチも含むフェッチ状態の場合にtrue

基本的な流れは

  • isLoadingを確認
  • isErrorを確認
  • successful状態をレンダリング

Query Keys

クエリキーに基づくクエリキャッシングを管理

文字列のみのクエリキー
文字列のクエリーキーが渡されると、クエリーキーの唯一の項目である文字列を持つ配列に内部で変換されます
この形式は、 以下の場合に便利です。

  • 一般的なリスト
  • 非階層的なリソース
 // A list of todos
 useQuery('todos', ...) // queryKey === ['todos']
 
 // Something else, whatever!
 useQuery('somethingSpecial', ...) // queryKey === ['somethingSpecial']

Arrayキー
クエリがそのデータを一意に記述するために、より多くの情報を必要とする場合、文字列と任意の数のシリアライズ可能なオブジェクトを持つ配列を使って記述することができる。以下のような場合に便利。

  • 階層型または入れ子型のリソース(IDやインデックスなど、アイテムを一意に特定するためのプリミティブを渡すのが一般的です)
  • パラメータを追加したクエリ(追加オプションのオブジェクトを渡すのが一般的です)
 // An individual todo
 useQuery(['todo', 5], ...)
 // queryKey === ['todo', 5]
 
 // An individual todo in a "preview" format
 useQuery(['todo', 5, { preview: true }], ...)
 // queryKey === ['todo', 5, { preview: true }]
 
 // A list of todos that are "done"
 useQuery(['todos', { type: 'done' }], ...)
 // queryKey === ['todos', { type: 'done' }]

クエリ関数が変数に依存する場合は、クエリキーにその変数を含めてください
クエリキーは、取得するデータを一意に記述するため、クエリ関数で使用する変数が変化する場合は、その変数を含める必要があります

 function Todos({ todoId }) {
   const result = useQuery(['todos', todoId], () => fetchTodoById(todoId))
 }

Query Functions

Query Functionは、文字通り、プロミスを返す関数であれば何でもよい。返されるプロミスは、データを解決するか、エラーを投げるかのどちらかでなければなりません。

useQuery(['todos'], fetchAllTodos)
 useQuery(['todos', todoId], () => fetchTodoById(todoId))
 useQuery(['todos', todoId], async () => {
   const data = await fetchTodoById(todoId)
   return data
 })
 useQuery(['todos', todoId], ({ queryKey }) => fetchTodoById(queryKey[1]))

fetchなどデフォルトでthrowしないクライアントでの使用方法

 useQuery(['todos', todoId], async () => {
   const response = await fetch('/todos/' + todoId)
   if (!response.ok) {
     throw new Error('Network response was not ok')
   }
   return response.json()
 })

Parallel Queries

フェッチの並行性を最大化するために、並行して、または同時に実行されるクエリです。
React QueryのuseQueryと useInfiniteQueryのフックをいくつでも並べて使うだけ

 function App () {
   // The following queries will execute in parallel
   const usersQuery = useQuery('users', fetchUsers)
   const teamsQuery = useQuery('teams', fetchTeams)
   const projectsQuery = useQuery('projects', fetchProjects)
   ...
 }

useQueriesによる動的な並列クエリ
レンダーごとに実行するクエリの数が変化する場合、フックのルールに反するため、手動でクエリを実行することはできません。その代わり、React QueryにはuseQueriesというフックが用意されている

 function App({ users }) {
   const userQueries = useQueries(
     users.map(user => {
       return {
         queryKey: ['user', user.id],
         queryFn: () => fetchUserById(user.id),
       }
     })
   )
 }

Dependent Queries

実行する前に前のクエリが終了することに依存しているクエリ。

Background Fetching Indicators

  • isFetchingを参照すればフェッチ状態であることを示すことができる
  • 個々のクエリのロード状態に加えて、いずれかのクエリがフェッチされているときに(バックグラウンドも含めて)グローバルなロードインジケーターを表示したい場合、useIsFetchingフックを使用することができます。

Window Focus Refetching

ユーザーがアプリケーションを離れて古いデータに戻った場合、React Queryはバックグラウンドで自動的に新しいデータを要求します。refetchOnWindowFocusオプションを使用して、グローバルまたはクエリごとにこれを無効にすることができます。

Disabling / Pausing Queries

クエリの自動実行を無効にしたい場合は、enabled = falseオプションを使用します。

Query Retries

useQueryクエリが失敗した場合(クエリ関数がエラーを投げる)、React Queryは、そのクエリのリクエストが連続再試行の最大数(デフォルトは3)に達していないか、再試行が許可されているかどうかを判断する関数が提供されていれば、自動的にそのクエリを再試行する。
リトライは、グローバルレベルと個々のクエリレベルの両方で設定することができます。

Paginated Queries

keepPreviousDataによるページ分割クエリの改善
クエリに対してpageIndex(またはカーソル)をインクリメントする場合、useQueryを使用すると技術的には問題なく動作しますが、ページやカーソルごとに異なるクエリが作成・破棄されるため、UIが成功状態や読み込み状態を行き来することになります。keepPreviousDataを trueに設定することで前のデータとシームレスに入れ替わるようになる。
https://react-query-v3.tanstack.com/guides/paginated-queries#better-paginated-queries-with-keeppreviousdata

Infinite Queries

既存のデータセットに追加的に「さらに」データをロードできるリストのレンダリングや「無限スクロール」も、非常に一般的なUIパターンです。React Queryは、useQueryの便利なバージョンであるuseInfiniteQueryをサポートしており、このようなタイプのリストをクエリすることができます。

Placeholder Query Data

Initial Query Data

クエリの初期データを必要な前にキャッシュに供給する方法はたくさんあります。
クエリにinitialDataを与え、キャッシュが空の場合に事前投入を行う

Prefetching

prefetchQueryメソッドを使用してクエリの結果をプリフェッチし、キャッシュに格納することができます。

const prefetchTodos = async () => {
   // The results of this query will be cached like a normal query
   await queryClient.prefetchQuery('todos', fetchTodos)
 }

Mutations

クエリとは異なり、変異は通常、データの作成/更新/削除やサーバーのサイドエフェクトを実行するために使用されます。

 function App() {
   const mutation = useMutation(newTodo => {
     return axios.post('/todos', newTodo)
   })
 
   return (
     <div>
       {mutation.isLoading ? (
         'Adding todo...'
       ) : (
         <>
           {mutation.isError ? (
             <div>An error occurred: {mutation.error.message}</div>
           ) : null}
 
           {mutation.isSuccess ? <div>Todo added!</div> : null}
 
           <button
             onClick={() => {
               mutation.mutate({ id: new Date(), title: 'Do Laundry' })
             }}
           >
             Create Todo
           </button>
         </>
       )}
     </div>
   )
 }

https://react-query-v3.tanstack.com/guides/mutations

Query Invalidation

QueryClientにはinvalidateQueriesメソッドがあり、クエリを古くなったと判断して再取得することができます。

Invalidation from Mutations

postTodoのMutationが成功したとき、すべてのTodoクエリを無効にして、新しいTodoアイテムを表示するようにリフェッチしたい場合があります。これを行うには、Mutationの onSuccessオプションと、クライアントの invalidateQueries関数を使用することができます。

 import { useMutation, useQueryClient } from 'react-query'
 
 const queryClient = useQueryClient()
 
 // When this mutation succeeds, invalidate any queries with the `todos` or `reminders` query key
 const mutation = useMutation(addTodo, {
   onSuccess: () => {
     queryClient.invalidateQueries('todos')
     queryClient.invalidateQueries('reminders')
   },
 })

Updates from Mutation Responses

Optimistic Updates

Query Cancellation

Scroll Restoration

Filters