Back to list
HuynhSang2005

tanstack-react-query

by HuynhSang2005

0🍴 0📅 Jan 25, 2026

SKILL.md


name: tanstack-react-query description: Cá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.

Score

Total Score

50/100

Based on repository quality metrics

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

Reviews

💬

Reviews coming soon