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

React TanStack Queryで初期データを複数のキャッシュへ分配する

管理画面の初期表示で、店舗、商品カテゴリ、配送方法など複数のデータが必要になる場合があります。

それぞれのAPIを同時に呼び出す代わりに、初期表示用APIでまとめて取得するとリクエスト数を減らせます。

今回はまとめて取得した結果を、TanStack Queryの個別キャッシュへ分配する方法を紹介します。

初期表示APIの型を定義する

types.ts
export type Store = { storeId: string; name: string };
export type Category = { categoryId: string; name: string };
export type ShippingMethod = { shippingMethodId: string; name: string };

export type ManagementBootstrap = {
    stores: Store[];
    categories: Category[];
    shippingMethods: ShippingMethod[];
};

初期表示APIは3種類の一覧を1つのレスポンスで返します。

個別の更新後は、店舗だけ、カテゴリだけという単位でも再取得できるものとします。

query keyを分ける

managementKeys.ts
export const managementKeys = {
    all: ['management'] as const,
    bootstrap: () => [...managementKeys.all, 'bootstrap'] as const,
    stores: () => [...managementKeys.all, 'stores'] as const,
    categories: () => [...managementKeys.all, 'categories'] as const,
    shippingMethods: () => [...managementKeys.all, 'shippingMethods'] as const,
};

初期表示用と個別データ用に別のquery keyを用意します。

取得結果を個別キャッシュへ保存する

useManagementData.ts
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { managementApi } from './managementApi';
import { managementKeys } from './managementKeys';
import type { Category, ShippingMethod, Store } from './types';

type Items<T> = { items: T[] };

export const useManagementData = () => {
    const queryClient = useQueryClient();

    const bootstrapQuery = useQuery({
        queryKey: managementKeys.bootstrap(),
        queryFn: async ({ signal }) => {
            const result = await managementApi.bootstrap(signal);

            queryClient.setQueryData<Items<Store>>(
                managementKeys.stores(),
                { items: result.stores },
            );
            queryClient.setQueryData<Items<Category>>(
                managementKeys.categories(),
                { items: result.categories },
            );
            queryClient.setQueryData<Items<ShippingMethod>>(
                managementKeys.shippingMethods(),
                { items: result.shippingMethods },
            );

            return result;
        },
    });

    const storesQuery = useQuery({
        queryKey: managementKeys.stores(),
        queryFn: ({ signal }) => managementApi.findStores(signal),
        enabled: bootstrapQuery.isSuccess,
    });

    const categoriesQuery = useQuery({
        queryKey: managementKeys.categories(),
        queryFn: ({ signal }) => managementApi.findCategories(signal),
        enabled: bootstrapQuery.isSuccess,
    });

    const shippingMethodsQuery = useQuery({
        queryKey: managementKeys.shippingMethods(),
        queryFn: ({ signal }) => managementApi.findShippingMethods(signal),
        enabled: bootstrapQuery.isSuccess,
    });

    const data = storesQuery.data && categoriesQuery.data && shippingMethodsQuery.data
        ? {
            stores: storesQuery.data.items,
            categories: categoriesQuery.data.items,
            shippingMethods: shippingMethodsQuery.data.items,
        }
        : undefined;

    return {
        data,
        error: bootstrapQuery.error,
        isPending: bootstrapQuery.isPending || data === undefined,
    };
};

初期表示APIの結果を受け取った時点で、3つの個別キャッシュへ値を保存します。

個別Queryは初期取得が成功してから有効になります。すでにキャッシュにデータがあるため、画面はその値をすぐ利用できます。

不要な直後の再取得を防ぐ

初期データをsetQueryDataで保存すると、そのデータは現在時刻に更新されたものとして扱われます。

ただし、staleTimeの初期値は0なので、個別Queryが有効になった直後に再取得する場合があります。

直後の再取得が不要なら、個別QueryへstaleTimeを設定します。

staleTimeを設定する
const storesQuery = useQuery({
    queryKey: managementKeys.stores(),
    queryFn: ({ signal }) => managementApi.findStores(signal),
    enabled: bootstrapQuery.isSuccess,
    staleTime: 30_000,
});

30秒以内は初期表示APIのデータを新しいものとして使用します。

個別更新後は必要なキャッシュだけ無効化する

店舗を更新した場合は、店舗一覧だけを再取得できます。

店舗更新後の処理
const updateStoreMutation = useMutation({
    mutationFn: managementApi.updateStore,
    onSuccess: async () => {
        await queryClient.invalidateQueries({
            queryKey: managementKeys.stores(),
        });
    },
});

初期表示APIをもう一度呼び出す必要はありません。

キャッシュの無効化については、以下の記事でも紹介しています。

更新後に必要なキャッシュを再取得する方法

まとめて取得する範囲に注意する

初期表示APIへ含めるデータが増え続けると、レスポンスが大きくなり、変更頻度の異なるデータが強く結び付きます。

以下の条件を満たすデータをまとめると扱いやすくなります。

  • 同じ画面の初期表示で必ず必要になる。
  • データ量が小さい。
  • 同じ権限で取得できる。
  • 個別の再取得APIも用意できる。

初期表示API全体が1つのHTTPレスポンスなら、通常は成功か失敗のどちらかです。

データごとに成功・失敗を返す場合は、個別キャッシュへ保存する条件や、画面に表示するエラーを決める必要があります。

初期取得をまとめつつ個別のquery keyへ分配すると、初回のリクエスト数を減らし、その後の更新は必要なデータだけ再取得できます。


関連記事