tanstack-react-query

HuynhSang2005's avatarfrom HuynhSang2005

Cách dùng @tanstack/react-query v5 trong repo này (QueryProvider, optimistic updates, invalidate sau mutation).

⭐0stars🔀0forks📁View on GitHub🕐Updated Jan 10, 2026

When & Why to Use This Skill

This Claude skill provides comprehensive guidelines and standardized patterns for implementing TanStack React Query v5 within a React ecosystem. It focuses on best practices for server-state management, including centralized QueryClient configuration, type-safe query keys using queryOptions, and efficient cache invalidation strategies to ensure high-performance data fetching and UI synchronization.

Use Cases

  • Setting up a centralized QueryProvider with integrated DevTools for optimized development and debugging workflows.
  • Implementing type-safe and reusable data fetching logic using the TanStack Query v5 queryOptions helper to reduce boilerplate code.
  • Executing complex mutations with optimistic updates to enhance user experience by providing immediate UI feedback before server confirmation.
  • Managing sophisticated cache invalidation and refetching logic to maintain data consistency across the application after state changes.
  • Standardizing error handling patterns and localized UI feedback for failed network requests within a React application.
nametanstack-react-query
descriptionCách dùng @tanstack/react-query v5 trong repo này (QueryProvider, optimistic updates, invalidate sau mutation).

Nguồn trong repo

  • Provider: apps/web/src/providers/query-provider.tsx
  • QueryClient: Centralized từ apps/web/src/lib/query-client.ts
  • Features hooks: apps/web/src/features/**/use-*.ts

Quy tắc dùng đúng

  • React Query là client-side server-state (không phải global cache).
  • Không dùng trong Server Components. Với Server Components, fetch trực tiếp (ví dụ qua Supabase services).
  • Ưu tiên centralized QueryClient từ query-provider.tsx, tránh tạo client mới.

Setup hiện tại (Dev Refactor 3.1)

// apps/web/src/lib/query-client.ts
export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // 60 seconds
      gcTime: 5 * 60 * 1000, // 5 minutes (v5 renamed from cacheTime)
      refetchOnWindowFocus: false,
      retry: 1,
    },
  },
})

Devtools Setup (Chi tiết)

Provider configuration:

// apps/web/src/providers/query-provider.tsx
'use client'

import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { useState } from 'react'

export function QueryProvider({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(() => new QueryClient({
    defaultOptions: {
      queries: {
        staleTime: 60 * 1000, // 1 phút
        gcTime: 5 * 60 * 1000, // 5 phút
        refetchOnWindowFocus: false,
        retry: 1,
      },
    },
  }))

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      {/* Devtools chỉ hiển thị trong development */}
      {process.env.NODE_ENV === 'development' && (
        <ReactQueryDevtools initialIsOpen={false} />
      )}
    </QueryClientProvider>
  )
}
  • Devtools: chỉ bật trong development (kiểm tra process.env.NODE_ENV).
  • Persister: Optional, có thể dùng persistQueryClient nếu cần offline support.

Pattern khuyến nghị

1. queryOptions() Helper (TanStack Query v5)

Tạo typed, reusable query configurations:

import { queryOptions } from '@tanstack/react-query'

// Basic usage
const blogPostsOptions = queryOptions({
  queryKey: ['blog', 'posts'] as const,
  queryFn: fetchBlogPosts,
})

// Với filters
const blogPostsByTagOptions = (tag: string) => queryOptions({
  queryKey: ['blog', 'posts', { tag }] as const,
  queryFn: () => fetchBlogPostsByTag(tag),
  staleTime: 5 * 60 * 1000, // 5 minutes
  gcTime: 30 * 60 * 1000, // 30 minutes
})

// Trong component
import { useQuery } from '@tanstack/react-query'
import { blogPostsByTagOptions } from './hooks/use-blog-posts'

function BlogList({ tag }: { tag: string }) {
  const { data, isLoading, error } = useQuery(blogPostsByTagOptions(tag))
  // ...
}

Benefits:

  • Type-safe query keys với as const
  • Reusable across components
  • Easy to invalidate: queryClient.invalidateQueries({ queryKey: ['blog', 'posts'] })

2. Query Keys Ổn định (CẬP NHẬT)

// ✅ Tốt: mảng ổn định với as const
const useBlogPosts = (filters?: BlogFilters) => {
  return useQuery(
    queryOptions({
      queryKey: ['blog', 'posts', filters] as const,
      queryFn: () => fetchBlogPosts(filters),
    })
  )
}

// ❌ Tránh: Không có type safety
const useBlogPosts = (filters?: BlogFilters) => {
  return useQuery({
    queryKey: ['blog', 'posts', filters],
    queryFn: () => fetchBlogPosts(filters),
  })
}

2. Invalidate Sau Mutation

const useCreatePost = () => {
  return useMutation({
    mutationFn: createPost,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['blog', 'posts'] })
    },
  })
}

3. Optimistic Updates (Khi cần)

const useUpdatePost = () => {
  return useMutation({
    mutationFn: updatePost,
    onMutate: async (newPost) => {
      // Cancel outgoing refetches
      await queryClient.cancelQueries({ queryKey: ['blog', 'posts', newPost.id] })

      // Snapshot previous value
      const previousPost = queryClient.getQueryData(['blog', 'posts', newPost.id])

      // Optimistically update
      queryClient.setQueryData(['blog', 'posts', newPost.id], newPost)

      return { previousPost }
    },
    onError: (err, newPost, context) => {
      // Rollback
      if (context?.previousPost) {
        queryClient.setQueryData(['blog', 'posts', newPost.id], context.previousPost)
      }
    },
    onSettled: (data, error, newPost) => {
      // Refetch after error or success
      queryClient.invalidateQueries({ queryKey: ['blog', 'posts', newPost.id] })
    },
  })
}

4. Error UI (Tiếng Việt)

const useBlogPosts = (filters?: BlogFilters) => {
  return useQuery({
    queryKey: ['blog', 'posts', filters],
    queryFn: () => fetchBlogPosts(filters),
    meta: {
      errorMessage: 'Không thể tải danh sách bài viết',
    },
  })
}

Tránh

  • "Cache chồng cache": nếu data đã được fetch server-side và render server, tránh refetch client trừ khi cần interactivity.
  • Dùng React Query để thay thế DB RLS/permission checks.
  • Tạo new QueryClient() mới trong mỗi component/hook.
  • Dùng queryKey dạng string thay vì mảng.

Debug

  • React Query Devtools: Mở rộng để xem query status, cache, và timing.
  • Query Keys: Dùng queryClient.getQueryData() để kiểm tra cache.