TanStack Query: Gerenciamento de Server State
Aula 1 de 5
Fundamentos do TanStack Query
O TanStack Query (anteriormente React Query) é a biblioteca mais popular para gerenciamento de estado assíncrono no ecossistema React. Ela abstrai fetching, caching, sincronização e atualizações de dados do servidor.
QueryClient e Provider
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutos
gcTime: 1000 * 60 * 30, // 30 minutos (antes cacheTime)
retry: 2,
refetchOnWindowFocus: true,
},
},
})
function App() {
return (
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)
}
useQuery — Consultas Básicas
import { useQuery } from '@tanstack/react-query'
function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error, isError } = useQuery({
queryKey: ['users', userId],
queryFn: async () => {
const res = await fetch(`/api/users/${userId}`)
if (!res.ok) throw new Error('Falha ao carregar')
return res.json()
},
staleTime: 1000 * 60 * 2,
})
if (isLoading) return <Spinner />
if (isError) return <Error message={error.message} />
return <div>{data.name}</div>
}
useMutation — Mutações e Invalidação
import { useMutation, useQueryClient } from '@tanstack/react-query'
function CreateUser() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (newUser: UserData) =>
fetch('/api/users', {
method: 'POST',
body: JSON.stringify(newUser),
headers: { 'Content-Type': 'application/json' },
}).then(r => r.json()),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['users'] })
},
})
return (
<button onClick={() => mutation.mutate({ name: 'João' })} disabled={mutation.isPending}>
{mutation.isPending ? 'Criando...' : 'Criar Usuário'}
</button>
)
}
Chaves de Query e Dependências
// Query key como dependência
function UserList({ page, search }: { page: number; search: string }) {
return useQuery({
queryKey: ['users', { page, search }],
queryFn: () => fetchUsers({ page, search }),
placeholderData: keepPreviousData, // mantém dados anteriores durante loading
})
}
Paginação e Infinite Queries
import { useInfiniteQuery } from '@tanstack/react-query'
function Feed() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useInfiniteQuery({
queryKey: ['feed'],
queryFn: ({ pageParam }) => fetchFeed(pageParam),
initialPageParam: 0,
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
})
return (
<div>
{data?.pages.map(page =>
page.items.map(item => <FeedItem key={item.id} item={item} />)
)}
<button onClick={() => fetchNextPage()} disabled={!hasNextPage}>
{isFetchingNextPage ? 'Carregando...' : 'Carregar mais'}
</button>
</div>
)
}
Optimistic Updates
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (newTodo) => {
await queryClient.cancelQueries({ queryKey: ['todos'] })
const previous = queryClient.getQueryData(['todos'])
queryClient.setQueryData(['todos'], (old: Todo[]) =>
old.map(t => t.id === newTodo.id ? { ...t, ...newTodo } : t)
)
return { previous }
},
onError: (err, newTodo, context) => {
queryClient.setQueryData(['todos'], context?.previous)
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
Lab: Aplicação de Dashboard
npm create vite@latest query-demo -- --template react-ts
cd query-demo
npm install @tanstack/react-query @tanstack/react-query-devtools
// Crie um dashboard com:
// 1. useQuery para listar posts de /api/posts
// 2. useMutation para criar post com optimistic update
// 3. Invalidate queries após mutação
// 4. ReactQueryDevtools para depuração
O TanStack Query elimina a necessidade de gerenciar loading/error/cache manualmente, centralizando o estado do servidor em uma camada previsível e performática.