Giới thiệu vấn đề cache invalidation

Tanstack Query quản lý dữ liệu async, nhưng khi cập nhật sản phẩm, danh sách vẫn giữ dữ liệu cũ nếu không invalidation đúng. Điều này gây lỗi hiển thị và giảm trải nghiệm người dùng.

Kỹ thuật invalidate query theo key

Sử dụng queryClient.invalidateQueries với key cụ thể sau khi thực hiện mutation. Khi key bao gồm tham số, cần truyền đúng cấu trúc để chỉ invalidate những query liên quan.

Ví dụ mutation cập nhật sản phẩm

import { useMutation, useQueryClient } from '@tanstack/react-query';
import axios from 'axios';

const useUpdateProduct = () => {
  const queryClient = useQueryClient();
  return useMutation(
    (data) => axios.put(`/api/products/${data.id}`, data),
    {
      onSuccess: (_, variables) => {
        // Invalidate list và detail của sản phẩm vừa sửa
        queryClient.invalidateQueries({ queryKey: ['products'] });
        queryClient.invalidateQueries({ queryKey: ['product', variables.id] });
      },
    }
  );
};

Cache sharing với pagination

Khi danh sách có pagination, key thường dạng ['products', { page, limit }]. Để invalidation toàn bộ trang, dùng queryClient.invalidateQueries({ queryKey: ['products'] }) mà không truyền params.

Thêm optimistic update để giảm độ trễ UI

Optimistic update cập nhật cache ngay trước khi server trả về, giảm thời gian chờ. Cần rollback khi request thất bại.

Code optimistic update

onMutate: async (newProduct) => {
  await queryClient.cancelQueries({ queryKey: ['products'] });
  const previous = queryClient.getQueryData(['products']);
  queryClient.setQueryData(['products'], (old) => {
    if (!old) return old;
    return {
      ...old,
      pages: old.pages.map((page) => {
        return {
          ...page,
          data: page.data.map((p) => (p.id === newProduct.id ? { ...p, ...newProduct } : p)),
        };
      }),
    };
  });
  return { previous };
},
onError: (err, newProduct, context) => {
  queryClient.setQueryData(['products'], context.previous);
},
onSettled: () => {
  queryClient.invalidateQueries({ queryKey: ['products'] });
},

Quản lý cache thời gian sống (stale time)

Đặt staleTime phù hợp để tránh refetch không cần thiết. Ví dụ, danh sách sản phẩm có thay đổi ít, đặt staleTime: 5 * 60 * 1000 (5 phút).

Query config

useQuery(['products', { page, limit }], fetchProducts, {
  staleTime: 300000, // 5 phút
  cacheTime: 600000, // 10 phút giữ trong bộ nhớ
});

Kết luận

Áp dụng invalidate theo key, optimistic update và cấu hình staleTime giúp dashboard ecommerce phản hồi nhanh, dữ liệu luôn đồng bộ. Tham khảo khóa học "[Full Course] Ecommerce Dashboard Fullstack Clone" tại đây.