zukucode
主にWEB関連の情報を技術メモとして発信しています。

React TanStack Queryで更新後のキャッシュを再取得する

TanStack Queryでデータを更新しても、取得済みの一覧や詳細は自動では書き換わりません。

更新後に古いデータが表示されないよう、関連するQueryを無効化して再取得します。

今回は商品一覧と商品詳細を例に、query keyの作り方と更新後の処理を紹介します。

query keyを1か所で定義する

一覧と詳細のキーを階層化します。

productKeys.ts
export 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.ts
import { 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.ts
import { 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.ts
import { 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で待ちます。画面遷移前に最新化を完了させたい場合は、onSuccessasyncにして待機します。

削除した詳細キャッシュを取り除く

削除後は対象の詳細が存在しないため、再取得ではなくキャッシュを削除します。

useDeleteProduct.ts
import { 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を階層化しておくと、更新の影響範囲に合わせてキャッシュを扱えます。


関連記事