This content is only available in Portuguese.
Not translated yet for this language.
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.
O que vamos aprender
- - O que é TanStack Query e por que usar
- - Configuração básica do QueryClient
- - Busca de dados com `useQuery`
- - Gerenciamento de estados (carregamento, erro, sucesso)
- - Atualização de dados com `useMutation`
- - Cache e invalidação de queries
- - Configurações avançadas (staleTime, cacheTime)
- - Paginação e busca infinita
- - Boas práticas e padrões recomendados
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.jsx
import 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 QueryClient
const 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.jsx
import { 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.jsx
import { 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.jsx
import { 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.jsx
import { 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 curto- Problema: 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 uso - Use
selectpara 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
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.



