Back

Usando TanStack Query para Busca de Dados Mais Inteligente no React

Usando TanStack Query para Busca de Dados Mais Inteligente no React

Se você já construiu aplicações React que se comunicam com APIs, conhece o padrão: useEffect para a busca, useState para os dados, outro useState para loading, talvez mais um para erros. Antes que você perceba, está gerenciando uma confusão emaranhada de estado apenas para exibir uma lista de usuários.

Essa abordagem manual funciona, mas é frágil. O que acontece quando o usuário navega para outra página e volta? Você refaz a busca? Usa dados desatualizados? E quanto às tentativas quando a rede falha? Estes não são casos extremos—são a realidade de aplicações em produção.

TanStack Query (anteriormente React Query) resolve esses problemas tratando o estado do servidor de forma diferente do estado do cliente. Em vez de buscar dados imperativamente, você declara o que precisa e deixa a biblioteca lidar com cache, sincronização e atualizações. Este artigo mostra como migrar da busca manual de dados para uma abordagem mais inteligente e declarativa que escala.

Principais Pontos

  • Substitua padrões manuais de useEffect + useState por hooks useQuery declarativos para código mais limpo e sustentável
  • Aproveite cache automático, refetch em background e deduplicação de requisições para melhorar a experiência do usuário
  • Use mutations com atualizações otimistas para criar UIs responsivas que parecem instantâneas
  • Implemente estratégias adequadas de invalidação de queries para manter dados sincronizados em toda sua aplicação
  • Evite armadilhas comuns como usar TanStack Query para estado local ou esquecer parâmetros dinâmicos nas chaves de query

O Problema com Busca Manual de Dados

Aqui está um componente React típico buscando dados:

import { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    
    async function fetchUsers() {
      setIsLoading(true);
      setError(null);
      
      try {
        const response = await fetch('/api/users');
        if (!response.ok) throw new Error('Failed to fetch');
        
        const data = await response.json();
        if (!cancelled) {
          setUsers(data);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message);
        }
      } finally {
        if (!cancelled) {
          setIsLoading(false);
        }
      }
    }
    
    fetchUsers();
    
    return () => { cancelled = true };
  }, []);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Isso funciona, mas veja o que estamos gerenciando:

  • Três peças separadas de estado
  • Lógica de cleanup para prevenir atualizações de estado em componentes desmontados
  • Sem cache—cada montagem dispara uma nova busca
  • Sem lógica de retry para requisições falhas
  • Nenhuma forma de saber se nossos dados estão desatualizados

Agora multiplique essa complexidade por dezenas de componentes. É aí que o TanStack Query entra.

Como o TanStack Query Torna a Busca de Dados Mais Inteligente

TanStack Query trata o estado do servidor como um cidadão de primeira classe. Em vez de orquestrar buscas manualmente, você descreve seus requisitos de dados declarativamente:

import { useQuery } from '@tanstack/react-query';

function UserList() {
  const { data: users, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(res => {
      if (!res.ok) throw new Error('Failed to fetch');
      return res.json();
    }),
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <ul>
      {users?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

Mesma funcionalidade, mas note o que está faltando:

  • Nenhum useState para dados, loading ou erros
  • Nenhum useEffect ou lógica de cleanup
  • Nenhuma sincronização manual de estado

Mas aqui está o que você ganha:

  • Cache automático: Navegue para fora e volte, veja resultados instantâneos
  • Refetch em background: Dados desatualizados são atualizados silenciosamente
  • Deduplicação de requisições: Múltiplos componentes podem compartilhar a mesma query
  • Lógica de retry integrada: Requisições falhas fazem retry automaticamente
  • Atualizações otimistas: UI atualiza antes da confirmação do servidor

Conceitos Fundamentais para Gerenciamento Inteligente de Dados

Queries: Lendo Estado do Servidor

Queries buscam e fazem cache de dados. O hook useQuery aceita um objeto de configuração com duas propriedades essenciais:

const { data, isLoading, error } = useQuery({
  queryKey: ['todos', userId, { status: 'active' }],
  queryFn: () => fetchTodosByUser(userId, { status: 'active' }),
  staleTime: 5 * 60 * 1000, // 5 minutos
  gcTime: 10 * 60 * 1000, // 10 minutos (anteriormente cacheTime)
});

Query Keys identificam unicamente cada query. São arrays que podem incluir:

  • Identificadores estáticos: ['users']
  • Parâmetros dinâmicos: ['user', userId]
  • Filtros complexos: ['todos', { status, page }]

Quando qualquer parte da query key muda, TanStack Query sabe que deve buscar dados frescos.

Mutations: Alterando Estado do Servidor

Enquanto queries leem dados, mutations os modificam:

import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreateTodo() {
  const queryClient = useQueryClient();
  
  const mutation = useMutation({
    mutationFn: (newTodo) => 
      fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify(newTodo),
        headers: { 'Content-Type': 'application/json' },
      }).then(res => {
        if (!res.ok) throw new Error('Failed to create todo');
        return res.json();
      }),
    onSuccess: () => {
      // Invalidar e refazer busca
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });

  return (
    <button 
      onClick={() => mutation.mutate({ title: 'New Todo' })}
      disabled={mutation.isPending}
    >
      {mutation.isPending ? 'Creating...' : 'Create Todo'}
    </button>
  );
}

Mutations lidam com o ciclo de vida completo: estados de loading, tratamento de erros e callbacks de sucesso. O callback onSuccess é perfeito para atualizar seu cache após mudanças.

Invalidação de Query: Mantendo Dados Frescos

Invalidação marca queries como desatualizadas, disparando refetches em background:

// Invalidar tudo
queryClient.invalidateQueries();

// Invalidar queries específicas
queryClient.invalidateQueries({ queryKey: ['todos'] });

// Invalidar com correspondência exata
queryClient.invalidateQueries({ 
  queryKey: ['todo', 5], 
  exact: true 
});

É assim que TanStack Query mantém sua UI sincronizada após mutations. Alterou um todo? Invalide a lista de todos. Atualizou um usuário? Invalide os dados daquele usuário.

Configurando TanStack Query na Sua Aplicação React

Primeiro, instale o pacote:

npm install @tanstack/react-query

Então envolva sua aplicação com o provider QueryClient:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000, // 1 minuto
      gcTime: 5 * 60 * 1000, // 5 minutos (anteriormente cacheTime)
      retry: 3,
      retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
    },
  },
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}

O QueryClient gerencia todo cache e sincronização. Você tipicamente cria uma instância para toda sua aplicação.

Padrões Avançados para Aplicações em Produção

Atualizações Otimistas

Atualize a UI imediatamente, depois sincronize com o servidor:

const mutation = useMutation({
  mutationFn: updateTodo,
  onMutate: async (newTodo) => {
    // Cancelar refetches pendentes
    await queryClient.cancelQueries({ queryKey: ['todos'] });
    
    // Snapshot do valor anterior
    const previousTodos = queryClient.getQueryData(['todos']);
    
    // Atualizar otimisticamente
    queryClient.setQueryData(['todos'], old => 
      old ? [...old, newTodo] : [newTodo]
    );
    
    // Retornar contexto para rollback
    return { previousTodos };
  },
  onError: (err, newTodo, context) => {
    // Rollback em caso de erro
    if (context?.previousTodos) {
      queryClient.setQueryData(['todos'], context.previousTodos);
    }
  },
  onSettled: () => {
    // Sempre refetch após erro ou sucesso
    queryClient.invalidateQueries({ queryKey: ['todos'] });
  },
});

Queries Dependentes

Encadeie queries que dependem umas das outras:

const { data: user } = useQuery({
  queryKey: ['user', email],
  queryFn: () => getUserByEmail(email),
});

const { data: projects } = useQuery({
  queryKey: ['projects', user?.id],
  queryFn: () => getProjectsByUser(user.id),
  enabled: !!user?.id, // Só executa quando user ID existe
});

Queries Infinitas

Lide com dados paginados com scroll infinito:

import { useInfiniteQuery } from '@tanstack/react-query';

const {
  data,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage,
} = useInfiniteQuery({
  queryKey: ['projects'],
  queryFn: ({ pageParam = 0 }) => fetchProjects({ page: pageParam }),
  getNextPageParam: (lastPage, pages) => lastPage.nextCursor,
  initialPageParam: 0,
});

Armadilhas Comuns e Soluções

Esquecer o QueryClientProvider

Se suas queries retornam undefined ou não funcionam de forma alguma, verifique se sua aplicação está envolvida com QueryClientProvider. Este é o erro de configuração mais comum.

Usar TanStack Query para Estado Local

TanStack Query é para estado do servidor. Não use para inputs de formulário, toggles de UI ou outro estado apenas do cliente. Use useState ou useReducer para esses casos.

Query Keys Estáticas com Dados Dinâmicos

Sempre inclua parâmetros dinâmicos nas suas query keys:

// ❌ Errado - mostrará os mesmos dados para todos os usuários
useQuery({
  queryKey: ['user'],
  queryFn: () => fetchUser(userId),
});

// ✅ Correto - entrada de cache única por usuário
useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
});

Conclusão

TanStack Query transforma a busca de dados no React de um processo manual e propenso a erros em um sistema declarativo e robusto. Ao tratar o estado do servidor como fundamentalmente diferente do estado do cliente, elimina boilerplate enquanto adiciona recursos poderosos como cache, sincronização e atualizações otimistas.

A mudança mental é simples: pare de pensar em como buscar dados e comece a pensar em quais dados você precisa. Declare seus requisitos através de query keys e funções, então deixe TanStack Query lidar com a complexidade de manter tudo sincronizado.

Comece pequeno—substitua um fetch com useEffect por useQuery. Uma vez que você veja os estados de loading instantâneos, retries automáticos e cache perfeito em ação, entenderá por que TanStack Query se tornou essencial para aplicações React modernas.

FAQs

São a mesma biblioteca. React Query foi renomeado para TanStack Query para refletir que agora suporta múltiplos frameworks além do React. O pacote específico para React ainda é mantido e usa a mesma API que você já conhece.

Sim, mas provavelmente você não precisa para estado do servidor. TanStack Query lida com cache e compartilhamento de dados entre componentes automaticamente. Use Redux ou Context para verdadeiro estado do cliente como preferências do usuário ou estado de UI, e TanStack Query para qualquer coisa que venha de uma API.

Por padrão, queries refazem busca no foco da janela, reconexão e montagem. Você controla a atualização com staleTime (quanto tempo os dados permanecem frescos) e gcTime (quanto tempo manter dados não utilizados). Queries desatualizadas refazem busca em background enquanto mostram dados em cache instantaneamente.

Invalidação marca queries como desatualizadas mas não refaz busca imediatamente—espera a query ser usada novamente. Refetch direto dispara uma requisição de rede imediata. Use invalidação após mutations para melhor performance, pois só refaz busca de queries que estão realmente sendo exibidas.

TanStack Query se destaca em atualizações quase em tempo real através de polling e intervalos de refetch. Para necessidades de tempo real verdadeiro, combine com WebSockets: use o socket para atualizações ao vivo e TanStack Query para carregamento inicial de dados e fallback quando o socket desconecta.

Sim. TanStack Query 5+ é totalmente compatível com React 19. As APIs usadas aqui (queries, mutations, invalidação) não mudaram, então os exemplos neste artigo funcionarão sem modificação.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers