This content is only available in Portuguese.

Not translated yet for this language.

How-To & Guides

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.

Equipe Blueprintblog8 min
Gerenciamento de Estado de Servidor com TanStack Query: Guia Prático

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:

jsx
// 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:

  • 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.

Conceito Fundamental 2: Buscando Dados com useQuery

O hook useQuery é usado para buscar e sincronizar dados:

jsx
// 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 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

Conceito Fundamental 3: Atualizando Dados com useMutation

useMutation é usado para criar, atualizar ou deletar dados:

jsx
// 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 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

Conceito Avançado: Configurações de Query

TanStack Query oferece muitas configurações para controlar o comportamento:

jsx
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

jsx
// 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

jsx
// 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

  1. 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
  2. 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
  3. 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
  4. 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

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 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

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.

Article tags

Related articles

Get the latest articles delivered to your inbox.

Follow Us: