Capítulo 33 de 456

TanStack Query

Core Idea

Use TanStack Query pra buscar dado em Client Components, fornecer dado inicial via <HydrationBoundary> a partir de Server Components, e coordenar mutations do browser com dado server cacheado através de query key/tag compartilhados. Com Cache Components, exige um helper de hidratação manual pra evitar leitura de Date.now() durante prerender.

Key Concepts

  • QueryClientProvider + getQueryClient(): um QueryClient novo por render server, um único reaproveitado no browser (typeof window === 'undefined' decide).
  • useQuery({queryKey, queryFn, enabled}): enabled atrasa a requisição até haver input, equivalente à key condicional do SWR.
  • useSuspenseQuery: delega loading ao Suspense boundary; propaga erro ao error boundary mais próximo.
  • prefetchQuery + dehydrate + <HydrationBoundary>: fornece dado do servidor; prefetchQuery não é awaitado (void) para não bloquear o render; shouldDehydrateQuery customizado inclui queries pending.
  • queryOptions(): helper que agrupa queryKey+queryFn+staleTime num contrato único reusado por prefetch e hook.
  • staleTime: evita refetch imediato no client após hidratação (equivalente a revalidateIfStale do SWR, mas com janela de tempo).
  • useMutation + onMutate/onError: update otimista manual: cancela queries em voo, salva valor anterior em context, escreve valor novo, reverte em onError.
  • dehydrate()Date.now(): com Cache Components isso causa current-time prerender error; use um helper próprio (getHydrationUpdatedAt com use cache) para timestamp cacheado por tag.

Code Examples

'use client'
let browserQueryClient: QueryClient | undefined
function getQueryClient() {
  if (typeof window === 'undefined') return new QueryClient()
  browserQueryClient ??= new QueryClient()
  return browserQueryClient
}
  • O que demonstra: padrão isolar client server-side, reaproveitar no browser.
import { queryOptions } from '@tanstack/react-query'
export const productCache = {
  key: (id: string) => ['product', id] as const,
  tag: (id: string) => `product:${id}`,
  options: (id: string) =>
    queryOptions({
      queryKey: productCache.key(id),
      queryFn: async () => {
        const res = await fetch(`/api/products/${id}`)
        if (!res.ok) throw new Error('Failed to fetch product')
        return res.json()
      },
      staleTime: 30_000,
    }),
}
  • O que demonstra: contrato único (key + tag + queryFn) reusado por prefetch server e hook client.
async function getHydrationUpdatedAt(tags: string[]) {
  'use cache'
  cacheTag(...tags)
  cacheLife('max')
  return Date.now()
}
  • O que demonstra: como cachear o timestamp de hidratação por tag pra evitar leitura de tempo não determinística durante prerender com Cache Components.

Reference Tables

CamadaOwnsControle
Next.js server cache (cacheLife)dado + output RSCrevalidate/expire
Next.js client cache (cacheLife)payload RSC prefetchedstale
TanStack Query browser cachevalor sob queryKeystaleTime, invalidateQueries, mutations

Anti-patterns

  • dehydrate() direto com Cache Components habilitado: lê Date.now() durante prerender e gera current-time prerender error. Use o helper de hidratação manual com use cache.
  • Múltiplas useSuspenseQuery no mesmo componente: rodam sequencialmente (waterfall); separe em componentes irmãos ou use useSuspenseQueries.
  • Query ativa fora de Suspense com Cache Components: TanStack Query lê o tempo atual ao criar query state; mantenha atrás de um boundary Suspense.

Key Takeaways

  1. TanStack Query 5.40.0+ suporta dehydratar queries pending (não awaited) pra Server Component prefetch.
  2. staleTime evita refetch client logo após hidratação; não precisa bater com cacheLife do servidor, que é camada independente.
  3. Com Cache Components habilitado, evite dehydrate() puro; construa o hydration state manualmente com timestamp cacheado por tag.
  4. Update otimista via onMutate/onError exige cancelar queries em voo antes de sobrescrever, senão uma resposta tardia pode sobrescrever o valor otimista.
  5. updateTag no Server Action invalida o server read cacheado com a tag do mesmo contrato usado no client.

Connects To

  • ch031 Client-side data fetching: framework de decisão (inline/Suspense/server-provided).
  • ch032 SWR: alternativa equivalente, mais simples, sem exigir helper manual de hidratação.
  • use cache / cacheTag / cacheLife / updateTag: mecanismo server coordenado via cache contract.