Gerenciamento de Estado de Servidor com TanStack Query: Guia Prático
Aprenda a usar o TanStack Query (anteriormente React Query) para gerenciar buscas de dados, cache, atualizações e sincronização em aplicações React, simplificando o gerenciamento de estado de servidor. Este guia cobre desde a configuração básica, useQuery, useMutation, até configurações avançadas, paginação e boas práticas para otimizar seu app.

Aprenda a usar o TanStack Query para gerenciar buscas de dados, cache, atualizações e sincronização em aplicações React, simplificando o gerenciamento de estado de servidor.
Introdução ao TanStack Query
TanStack Query (anteriormente React Query) é uma biblioteca poderosa para gerenciar o estado de servidor em aplicações React. Ela lida com busca, cache, sincronização e atualização de dados, permitindo que você se concentre na lógica da sua aplicação.
Analogia: Imagine que seu aplicativo React é um restaurante. TanStack Query é como o gerente de cozinha que:
Sabe quais pratos estão disponíveis (cache)
Verifica se os ingredientes estão frescos (staleTime)
Solicita novos ingredientes quando necessário (refetch)
Lida com pedidos especiais (mutations)
Notifica quando algo está esgotado (erro)
Conceito Fundamental 1: Configuração Básica
Primeiro, precisamos configurar o QueryClient e envolver nossa aplicação:
// src/main.jsximport React from 'react';import ReactDOM from 'react-dom/client';import { QueryClient, QueryClientProvider } from '@tanstack/react-query';import App from './App';// Cria uma instância do QueryClientconst queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60 * 5, // 5 minutos refetchOnWindowFocus: false, }, },});ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> </React.StrictMode>);Explicação:
QueryClientgerencia o cache e as operaçõesQueryClientProviderdisponibiliza o client para todos os componentesdefaultOptionsdefine configurações globais
Dica: Mantenha uma única instância do QueryClient para toda a aplicação.
Conceito Fundamental 2: Buscando Dados com useQuery
O hook useQuery é usado para buscar e sincronizar dados:
// src/components/UserList.jsximport { useQuery } from '@tanstack/react-query';const fetchUsers = async () => { const response = await fetch('https://api.example.com/users'); if (!response.ok) { throw new Error('Falha ao buscar usuários'); } return response.json();};export const UserList = () => { const { data, error, isLoading, isError } = useQuery({ queryKey: ['users'], // Chave única para identificar esta query queryFn: fetchUsers, // Função que busca os dados }); if (isLoading) { return <div>Carregando...</div>; } if (isError) { return <div>Erro: {error.message}</div>; } return ( <div> <h1>Usuários</h1> <ul> {data.map(user => ( <li key={user.id}>{user.name}</li> ))} </ul> </div> );};Explicação:
queryKey: Identificador único para esta query (organiza o cache)queryFn: Função assíncrona que retorna os dados ou lança erroisLoading: Indica se a query está carregando pela primeira vezisError: Indica se ocorreu um erroerror: Objeto de erro seisErrorfor truedata: Os dados retornados pela query
Resultado esperado:
Inicialmente exibe "Carregando..."
Após a busca, exibe uma lista de usuários ou mensagem de erro
Conceito Fundamental 3: Atualizando Dados com useMutation
useMutation é usado para criar, atualizar ou deletar dados:
// src/components/AddUser.jsximport { useMutation, useQueryClient } from '@tanstack/react-query';const addUser = async (newUser) => { const response = await fetch('https://api.example.com/users', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(newUser), }); if (!response.ok) { throw new Error('Falha ao adicionar usuário'); } return response.json();};export const AddUser = () => { const queryClient = useQueryClient(); const [name, setName] = useState(''); const mutation = useMutation({ mutationFn: addUser, onSuccess: () => { // Invalida a query de usuários para forçar refetch queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); const handleSubmit = (e) => { e.preventDefault(); mutation.mutate({ name }); setName(''); }; return ( <form onSubmit={handleSubmit}> <input type="text" value={name} onChange={(e) => setName(e.target.value)} placeholder="Nome do usuário" /> <button type="submit" disabled={mutation.isLoading}> {mutation.isLoading ? 'Adicionando...' : 'Adicionar Usuário'} </button> {mutation.isError && <div>Erro: {mutation.error.message}</div>} </form> );};Explicação:
useQueryClient: Acessa o cliente para invalidar queriesmutationFn: Função que executa a mutaçãoonSuccess: Callback executado quando a mutação é bem-sucedidamutate: Função para acionar a mutaçãoisLoading: Indica se a mutação está em andamento
Conceito Avançado: Configurações de Query
TanStack Query oferece muitas configurações para controlar o comportamento:
const { data } = useQuery({ queryKey: ['posts'], queryFn: fetchPosts, // Dados são considerados frescos por 5 minutos staleTime: 1000 * 60 * 5, // Dados são mantidos no cache por 10 minutos após ficarem obsoletos cacheTime: 1000 * 60 * 10, // Refetch quando a janela ganhar foco refetchOnWindowFocus: true, // Refetch quando a conexão for restabelecida refetchOnReconnect: true, // Refetch quando a página for retomada refetchOnMount: true, // Busca em segundo plano (a cada 30 segundos) refetchInterval: 1000 * 30, // Desativa busca em segundo plano quando a aba estiver inativa refetchIntervalInBackground: false,});Exemplo Prático 1: Paginação
// src/components/PaginatedPosts.jsximport { useQuery } from '@tanstack/react-query';const fetchPosts = async (page = 1, limit = 10) => { const response = await fetch( `https://api.example.com/posts?page=${page}&limit=${limit}` ); if (!response.ok) { throw new Error('Falha ao buscar posts'); } return response.json();};export const PaginatedPosts = () => { const [page, setPage] = useState(1); const { data, isLoading, isError } = useQuery({ queryKey: ['posts', page], // Inclui a página na chave queryFn: () => fetchPosts(page), keepPreviousData: true, // Mantém dados da página anterior }); if (isLoading) return <div>Carregando...</div>; if (isError) return <div>Erro ao carregar posts</div>; return ( <div> <h1>Posts</h1> <ul> {data.posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> <div> <button onClick={() => setPage(page - 1)} disabled={page === 1}> Anterior </button> <span> Página {page} </span> <button onClick={() => setPage(page + 1)}> Próximo </button> </div> </div> );};Exemplo Prático 2: Busca Infinita
// src/components/InfinitePosts.jsximport { useInfiniteQuery } from '@tanstack/react-query';const fetchPosts = async ({ pageParam = 1 }) => { const response = await fetch( `https://api.example.com/posts?page=${pageParam}` ); if (!response.ok) { throw new Error('Falha ao buscar posts'); } return response.json();};export const InfinitePosts = () => { const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status, } = useInfiniteQuery({ queryKey: ['infinite-posts'], queryFn: fetchPosts, getNextPageParam: (lastPage) => lastPage.nextPage, initialPageParam: 1, }); if (status === 'loading') return <div>Carregando...</div>; if (status === 'error') return <div>Erro ao carregar posts</div>; return ( <div> <h1>Posts Infinitos</h1> <div> {data.pages.map((page, i) => ( <div key={i}> {page.posts.map(post => ( <div key={post.id}>{post.title}</div> ))} </div> ))} </div> <button onClick={() => fetchNextPage()} disabled={!hasNextPage || isFetchingNextPage} > {isFetchingNextPage ? 'Carregando mais...' : hasNextPage ? 'Carregar Mais' : 'Nada mais para carregar'} </button> </div> );};Erros Comuns e Como Evitar
Não invalidar queries após mutations
Problema: Os dados exibidos não refletem as alterações recentes
Solução: Use
onSuccessemuseMutationpara invalidar queries relevantes
Chaves de query inconsistentes
Problema: O mesmo endpoint com parâmetros diferentes é tratado como a mesma query
Solução: Inclua todos os parâmetros relevantes na
queryKey
Esquecer de tratar estados de erro
Problema: A aplicação quebra quando uma falha de rede ocorre
Solução: Sempre verifique
isErroreerrorem seus componentes
Usar
staleTimemuito curtoProblema: Muitas requisições desnecessárias ao servidor
Solução: Ajuste o
staleTimecom base na frequência de atualização dos dados
Boas Práticas
Use chaves de query específicas e previsíveis
Mantenha as funções de busca puras e sem efeitos colaterais
Trate todos os estados possíveis (loading, error, success)
Configure
staleTimeecacheTimeapropriadamente para seu caso de usoUse
selectpara transformar dados no componente quando necessárioPrefira invalidar queries em vez de refetch manualmente
Documente suas chaves de query para facilitar a manutenção
Para ir além
Takeaway: TanStack Query simplifica drasticamente o gerenciamento de estado de servidor em React. Ao entender os conceitos fundamentais de queries, mutations e cache, você pode criar aplicações mais responsivas, resilientes e fáceis de manter. Comece com as configurações básicas e explore gradualmente os recursos avançados conforme suas necessidades crescem.


