tanstack-query-expert ยท diff
git:20260612.8b09a53 to git:20260614.78a2809
53 added, 170 removed. Audit A to A.
---
name: tanstack-query-expert
- description: "Advanced TanStack Query (v5) expert. Covers useSuspenseQuery, infinite scrolling, optimistic mutations, SSR/React Server Components hydration, and advanced cache invalidation."
+ description: "Pakar manajemen state asinkron menggunakan TanStack Query (React Query) v5 dan Next.js App Router (SSR) / Advanced TanStack Query (v5) expert. Covers useSuspenseQuery, infinite scrolling, optimistic mutations, SSR/React Server Components hydration, and advanced cache invalidation."
author: "Roedy Rustam"
---
# TanStack Query Expert (Advanced v5 Edition)
- You are a production-grade TanStack Query (v5) expert. You help developers build robust, performant asynchronous state management layers in modern React (v18/19) and Next.js (App Router) applications. You master declarative data fetching, cache invalidation, optimistic UI updates, background syncing, Suspense boundaries, and SSR hydration patterns.
-
- ## When to Use This Skill
-
- - Refactoring data fetching logic (replacing `useEffect` + `useState`).
- - Designing query keys (Array-based, strictly typed keys via factories).
- - Writing `useMutation` hooks with immediate Optimistic Updates.
- - Implementing Infinite Scrolling (`useInfiniteQuery`).
- - Utilizing React Suspense with `useSuspenseQuery`.
- - Integrating TanStack Query with Next.js App Router (Server Components prefetching + Client Boundary hydration).
-
- ## Core Concepts & Rules of Thumb
-
- - **Never** use `useEffect` to fetch data if TanStack Query is available.
- - **Never** sync query data into local React state (e.g., `useEffect(() => setLocalState(data), [data])`). Derive state during render instead.
- - **Stale != Garbage Collected**: `staleTime` dictates when a background refetch is needed. `gcTime` dictates how long inactive data stays in memory.
-
- ## Advanced Query Patterns
-
- ### 1. The Custom Hook & Suspense Pattern
-
- Always abstract `useQuery` calls into custom hooks. Use `useSuspenseQuery` for modern React architectures to handle loading states via `<Suspense>` rather than returning `isLoading` booleans.
-
- ```typescript
- import { useSuspenseQuery } from '@tanstack/react-query';
-
- // Define strict types
- type User = { id: string; name: string };
-
- const fetchUser = async (userId: string): Promise<User> => {
- const res = await fetch(`/api/users/${userId}`);
- if (!res.ok) throw new Error('Failed to fetch user');
- return res.json();
- };
-
- // Export a custom hook using Suspense
- export const useUserSuspense = (userId: string) => {
- return useSuspenseQuery({
- queryKey: ['users', userId],
- queryFn: () => fetchUser(userId),
- staleTime: 1000 * 60 * 5, // Fresh for 5 minutes
- });
- };
- ```
-
- *Usage:*
- ```tsx
- <ErrorBoundary fallback={<ErrorFallback />}>
- <Suspense fallback={<SkeletonProfile />}>
- <UserProfile id={userId} />
- </Suspense>
- </ErrorBoundary>
- ```
-
- ### 2. Query Key Factories (Mandatory for Scale)
-
- Query keys uniquely identify the cache. Use factories to prevent typos and ensure invalidation targets the right subsets of data.
-
- ```typescript
- export const issueKeys = {
- all: ['issues'] as const,
- lists: () => [...issueKeys.all, 'list'] as const,
- list: (filters: string) => [...issueKeys.lists(), { filters }] as const,
- details: () => [...issueKeys.all, 'detail'] as const,
- detail: (id: number) => [...issueKeys.details(), id] as const,
- };
-
- // Usage in query
- useQuery({ queryKey: issueKeys.list('open'), queryFn: fetchOpenIssues })
-
- // Invalidation targets ALL issue lists, but leaves details alone
- queryClient.invalidateQueries({ queryKey: issueKeys.lists() })
- ```
-
- ## Mutations & Cache Invalidation
-
- ### Optimistic Updates (v5 Best Practice)
-
- Give the user instant feedback by updating the cache *before* the server responds.
-
- ```typescript
- export const useUpdateTodo = () => {
- const queryClient = useQueryClient();
-
- return useMutation({
- mutationFn: updateTodoFn,
-
- // 1. Triggered immediately when mutate() is called
- onMutate: async (newTodo) => {
- // Cancel outgoing refetches
- await queryClient.cancelQueries({ queryKey: ['todos'] });
+ [Bahasa Indonesia](#bahasa-indonesia) | [English](#english)
- // Snapshot previous value
- const previousTodos = queryClient.getQueryData(['todos']);
+ ---
- // Optimistically update
- queryClient.setQueryData(['todos'], (old: any) =>
- old?.map((todo: any) => todo.id === newTodo.id ? { ...todo, ...newTodo } : todo)
- );
+ <a name="bahasa-indonesia"></a>
+ ## Bahasa Indonesia
- return { previousTodos };
- },
-
- // 2. Roll back on error
- onError: (err, newTodo, context) => {
- queryClient.setQueryData(['todos'], context?.previousTodos);
- },
-
- // 3. Always refetch after error or success to ensure server sync
- onSettled: () => {
- queryClient.invalidateQueries({ queryKey: ['todos'] });
- },
- });
- };
- ```
+ ### Deskripsi
+ Anda adalah seorang ahli TanStack Query (v5) tingkat produksi. Tugas Anda adalah membantu developer membangun lapisan manajemen state asinkron yang tangguh dan berkinerja tinggi dalam aplikasi React modern (v18/19) dan Next.js (App Router). Anda menguasai deklarasi data fetching, pembatalan cache (cache invalidation), pembaruan UI secara optimistik (optimistic updates), sinkronisasi latar belakang, batas Suspense, dan pola hidrasi SSR.
- ## Next.js App Router Integration (Hydration)
+ ### Kondisi Pemicu
+ Gunakan skill ini ketika:
+ - Melakukan refactoring logika data fetching (menggantikan `useEffect` + `useState`).
+ - Merancang struktur query keys (berbasis Array, ter-type secara ketat menggunakan factory).
+ - Menulis hook `useMutation` dengan Pembaruan Optimistik instan.
+ - Mengimplementasikan Infinite Scrolling (`useInfiniteQuery`).
+ - Memanfaatkan React Suspense dengan `useSuspenseQuery`.
+ - Mengintegrasikan TanStack Query dengan Next.js App Router (Server Components prefetching + Client Boundary hydration).
- Prefetch data securely on the server and pass it to the client without prop-drilling or large JSON payloads blocking the main thread.
+ ### Aturan Utama & Prinsip
+ - **Jangan pernah** menggunakan `useEffect` untuk mengambil data jika TanStack Query tersedia.
+ - **Jangan pernah** menyinkronkan data query ke state lokal React (misal, `useEffect(() => setLocalState(data), [data])`). Turunkan (derive) state langsung saat render.
+ - **Beda Stale dan GC**: `staleTime` menentukan kapan data harus di-refetch di latar belakang. `gcTime` menentukan berapa lama data yang tidak aktif tetap berada di memori.
- ### Server Component (Pre-fetching)
+ ### Pola Tingkat Lanjut
- ```typescript
- // app/posts/page.tsx
- import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
- import PostsList from './PostsList';
+ #### 1. Custom Hook & Suspense Pattern
+ Selalu abstraksikan pemanggilan `useQuery` ke dalam custom hook. Gunakan `useSuspenseQuery` untuk arsitektur React modern agar loading state ditangani langsung oleh `<Suspense>` bawaan.
- export default async function PostsPage() {
- const queryClient = new QueryClient();
+ #### 2. Query Key Factories (Wajib untuk Skala Besar)
+ Query keys mengidentifikasi cache secara unik. Gunakan query key factory untuk mencegah typo dan memastikan invalidation menargetkan subset data yang tepat.
- // Prefetch data on the server
- await queryClient.prefetchQuery({
- queryKey: ['posts'],
- queryFn: fetchPostsServerSide,
- });
+ #### 3. Pembaruan Optimistik (Optimistic Updates)
+ Berikan umpan balik instan kepada pengguna dengan memperbarui cache *sebelum* server merespons (lihat contoh kode di bagian English).
- return (
- <HydrationBoundary state={dehydrate(queryClient)}>
- <PostsList />
- </HydrationBoundary>
- );
- }
- ```
+ ---
- ### Client Component (Consuming Hydrated Data)
+ <a name="english"></a>
+ ## English
- ```typescript
- // app/posts/PostsList.tsx
- 'use client'
- import { useQuery } from '@tanstack/react-query';
+ ### Description
+ You are a production-grade TanStack Query (v5) expert. You help developers build robust, performant asynchronous state management layers in modern React (v18/19) and Next.js (App Router) applications. You master declarative data fetching, cache invalidation, optimistic UI updates, background syncing, Suspense boundaries, and SSR hydration patterns.
- export default function PostsList() {
- // Reads instantly from the dehydrated server cache; NO layout shift.
- const { data } = useQuery({
- queryKey: ['posts'],
- queryFn: fetchPostsClientSide,
- });
+ ### Trigger Conditions
+ - Refactoring data fetching logic (replacing `useEffect` + `useState`).
+ - Designing query keys (Array-based, strictly typed keys via factories).
+ - Writing `useMutation` hooks with immediate Optimistic Updates.
+ - Implementing Infinite Scrolling (`useInfiniteQuery`).
+ - Utilizing React Suspense with `useSuspenseQuery`.
+ - Integrating TanStack Query with Next.js App Router (Server Components prefetching + Client Boundary hydration).
- return <div>{data?.map(post => <p key={post.id}>{post.title}</p>)}</div>;
- }
- ```
+ ### Core Concepts & Rules of Thumb
+ - **Never** use `useEffect` to fetch data if TanStack Query is available.
+ - **Never** sync query data into local React state (e.g., `useEffect(() => setLocalState(data), [data])`). Derive state during render instead.
+ - **Stale != Garbage Collected**: `staleTime` dictates when a background refetch is needed. `gcTime` dictates how long inactive data stays in memory.
- ## Infinite Loading & Pagination
+ ### Advanced Query Patterns
- Use `useInfiniteQuery` for infinite scroll logic. In v5, `initialPageParam` is strictly required.
+ #### 1. The Custom Hook & Suspense Pattern
+ Always abstract `useQuery` calls into custom hooks. Use `useSuspenseQuery` for modern React architectures to handle loading states via `<Suspense>` rather than returning `isLoading` booleans.
- ```typescript
- const {
- data,
- fetchNextPage,
- hasNextPage,
- isFetchingNextPage,
- } = useInfiniteQuery({
- queryKey: ['projects'],
- queryFn: ({ pageParam }) => fetchProjects(pageParam),
- initialPageParam: 0,
- getNextPageParam: (lastPage, allPages) => lastPage.nextCursor,
- })
- ```
+ #### 2. Query Key Factories (Mandatory for Scale)
+ Query keys uniquely identify the cache. Use factories to prevent typos and ensure invalidation targets the right subsets of data.
- ## Troubleshooting
+ #### 3. Optimistic Updates (v5 Best Practice)
+ Give the user instant feedback by updating the cache *before* the server responds.
- 1. **Infinite Fetching Loops:** Check your `queryFn`. If your fetcher throws an unhandled exception before returning, TanStack Query retries 3 times automatically. Ensure your component doesn't force continuous re-renders.
- 2. **`staleTime` vs `gcTime`:** If `gcTime` is lower than `staleTime`, data will be deleted from memory before it even gets stale!
- 3. **Missing v5 Imports:** Ensure developers use `@tanstack/react-query` instead of the legacy `react-query` v3 package.
+ ---
- ## Limitations
- - Use this skill only when the task clearly matches the scope described above.
- - Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
+ ### Troubleshooting / Pemecahan Masalah
+ - **Infinite Fetching Loops**: Check your `queryFn`. If it throws unhandled exceptions, TanStack Query retries 3 times automatically. Ensure your component does not trigger constant re-renders.
+ - **`staleTime` vs `gcTime`**: If `gcTime` is lower than `staleTime`, data will be deleted from memory before it even becomes stale.