React TanStack Queryで更新後のキャッシュを再取得する
TanStack Queryでデータを更新しても、取得済みの一覧や詳細は自動では書き換わりません。
更新後に古いデータが表示されないよう、関連するQueryを無効化して再取得します。
今回は商品一覧と商品詳細を例に、query keyの作り方と更新後の処理を紹介します。
query keyを1か所で定義する
一覧と詳細のキーを階層化します。
productKeys.tsexport const productKeys = {
all: ['products'] as const,
lists: () => [...productKeys.all, 'list'] as const,
list: (category: string) => [...productKeys.lists(), { category }] as const,
details: () => [...productKeys.all, 'detail'] as const,
detail: (productId: string) => [...productKeys.details(), productId] as const,
};先頭部分を共有すると、対象範囲を選んで無効化できます。
| 指定するキー | 対象 |
|---|---|
productKeys.all | 商品に関するすべてのQuery |
productKeys.lists() | 条件が異なる商品一覧すべて |
productKeys.detail('p1') | 商品p1の詳細 |
一覧と詳細を取得する
productApiは、一覧取得、詳細取得、登録、更新、削除を行うAPIクライアントを表します。以下の例では、各メソッドがHTTPエラー時に例外を投げ、成功時にJSONを返すものとします。
productQueries.tsimport { useQuery } from '@tanstack/react-query';
import { productApi } from './productApi';
import { productKeys } from './productKeys';
export const useProducts = (category: string) => useQuery({
queryKey: productKeys.list(category),
queryFn: () => productApi.findAll(category),
});
export const useProduct = (productId: string | undefined) => useQuery({
queryKey: productKeys.detail(productId ?? ''),
enabled: productId !== undefined,
queryFn: () => {
if (!productId) throw new Error('productId is required.');
return productApi.find(productId);
},
});絞り込み条件もquery keyへ含めます。含めない場合、異なる条件の一覧が同じキャッシュを共有してしまいます。
登録後に一覧を再取得する
商品を登録した場合は一覧が変わるため、すべての商品一覧を無効化します。
useCreateProduct.tsimport { useMutation, useQueryClient } from '@tanstack/react-query';
import { productApi } from './productApi';
import { productKeys } from './productKeys';
export const useCreateProduct = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: productApi.create,
onSuccess: async () => {
await queryClient.invalidateQueries({
queryKey: productKeys.lists(),
});
},
});
};invalidateQueriesは対象を古いデータとして扱い、表示中のQueryを再取得します。
更新後に一覧と詳細を再取得する
商品名や価格を更新した場合、一覧と詳細の両方へ影響します。
useUpdateProduct.tsimport { useMutation, useQueryClient } from '@tanstack/react-query';
import { productApi } from './productApi';
import { productKeys } from './productKeys';
export const useUpdateProduct = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: productApi.update,
onSuccess: async (_result, variables) => {
await Promise.all([
queryClient.invalidateQueries({ queryKey: productKeys.lists() }),
queryClient.invalidateQueries({
queryKey: productKeys.detail(variables.productId),
}),
]);
},
});
};onSuccessの第2引数から、mutationへ渡した値を取得できます。
互いに依存しない再取得はPromise.allで待ちます。画面遷移前に最新化を完了させたい場合は、onSuccessをasyncにして待機します。
削除した詳細キャッシュを取り除く
削除後は対象の詳細が存在しないため、再取得ではなくキャッシュを削除します。
useDeleteProduct.tsimport { useMutation, useQueryClient } from '@tanstack/react-query';
import { productApi } from './productApi';
import { productKeys } from './productKeys';
export const useDeleteProduct = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: productApi.delete,
onSuccess: async (_result, productId) => {
queryClient.removeQueries({
queryKey: productKeys.detail(productId),
});
await queryClient.invalidateQueries({
queryKey: productKeys.lists(),
});
},
});
};詳細画面を表示したまま削除する場合は、削除後に一覧へ移動してからキャッシュを削除するなど、画面遷移との順序も決めます。
setQueryDataとの使い分け
更新APIが更新後の商品を返す場合は、詳細キャッシュを直接更新できます。
詳細キャッシュを直接更新するonSuccess: async (updatedProduct) => {
queryClient.setQueryData(
productKeys.detail(updatedProduct.productId),
updatedProduct,
);
await queryClient.invalidateQueries({ queryKey: productKeys.lists() });
},詳細は即時に更新し、並び順や件数への影響がある一覧はサーバーから再取得しています。
毎回productKeys.allを無効化すると、変更と関係のない詳細まで再取得されます。
更新によって変化するデータを確認し、一覧、特定の詳細、関連する集計など必要な範囲を指定します。
確認するポイント
| 操作 | キャッシュ処理 |
|---|---|
| 登録 | 一覧を無効化 |
| 更新 | 一覧と対象の詳細を無効化 |
| 削除 | 対象の詳細を削除し、一覧を無効化 |
| 更新結果をAPIが返す | 詳細へsetQueryDataも利用可能 |
query keyを階層化しておくと、更新の影響範囲に合わせてキャッシュを扱えます。