TanStack Query v4
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に設定することで前のデータとシームレスに入れ替わるようになる。
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>
)
}
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')
},
})