Aprenda a usar o TanStack Query para gerenciar cache, sincronização e estados de servidor em aplicações React de forma simples e eficiente.

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.
Cansado de gerenciar manualmente estados de carregamento, tratar erros de rede e implementar lógica complexa de cache em cada componente? O TanStack Query, anteriormente conhecido como React Query, elimina essa carga operacional ao automatizar a busca, sincronização e atualização de dados, permitindo que você foque inteiramente na lógica da sua aplicação React.
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)
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:
QueryClient gerencia o cache e as operações
QueryClientProvider disponibiliza o client para todos os componentes
defaultOptions define configurações globais
Dica: Mantenha uma única instância do QueryClient para toda a aplicação.
useQueryO 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 erro
isLoading: Indica se a query está carregando pela primeira vez
isError: Indica se ocorreu um erro
error: Objeto de erro se isError for true
data: Os dados retornados pela query
Resultado esperado:
Inicialmente exibe "Carregando..."
Após a busca, exibe uma lista de usuários ou mensagem de erro
useMutationuseMutation é 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 queries
mutationFn: Função que executa a mutação
onSuccess: Callback executado quando a mutação é bem-sucedida
mutate: Função para acionar a mutação
isLoading: Indica se a mutação está em andamento
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,});// 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> );};// 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> );};Não invalidar queries após mutations
Problema: Os dados exibidos não refletem as alterações recentes
Solução: Use onSuccess em useMutation para 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 isError e error em seus componentes
Usar staleTime muito curto
Problema: Muitas requisições desnecessárias ao servidor
Solução: Ajuste o staleTime com base na frequência de atualização dos dados
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 staleTime e cacheTime apropriadamente para seu caso de uso
Use select para transformar dados no componente quando necessário
Prefira invalidar queries em vez de refetch manualmente
Documente suas chaves de query para facilitar a manutenção
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.