Voltar para Artigos
Boas Práticas

Gerenciando Estado Remoto no React com TanStack Query

Fábio Flauzino
Fábio Flauzino
Senior Front-End Engineer
24 de jul. de 2026
5 min de leitura
Gerenciando Estado Remoto no React com TanStack Query

Série: Arquitetura Moderna para CRUDs no Next.js

  • ✅ Parte 1 — Arquitetando um CRUD Escalável
  • ✅ Parte 2 — Construindo uma Camada Profissional de Comunicação
  • 👉 Parte 3 — Gerenciando Estado Remoto com TanStack Query (Você está aqui)
  • ⏳ Parte 4 — Formulários Escaláveis com React Hook Form + Zod
  • ⏳ Parte 5 — Search Params, Filtros e Paginação
  • ⏳ Parte 6 — UX Moderna: Optimistic Updates e Performance

Estado Remoto: o problema que ainda não resolvemos

Nos artigos anteriores construímos uma arquitetura sólida para comunicação com a API.

Hoje nossa aplicação possui uma separação clara de responsabilidades:

Componentes
    ↓
Server Actions
    ↓
Services
    ↓
Axios
    ↓
API

Tudo está organizado.

Cada camada possui uma única responsabilidade e o código ficou muito mais fácil de manter.

Entretanto, existe um problema que ainda não resolvemos.


O mesmo dado sendo buscado várias vezes

Imagine uma página com três componentes diferentes.

A listagem principal precisa buscar usuários.

const users = await getUsersService();

Um painel lateral também precisa dessa lista.

const users = await getUsersService();

E um modal de seleção faz exatamente a mesma chamada.

const users = await getUsersService();

Quantas requisições HTTP foram feitas?

Resposta: três.

Mesmo endpoint.

Mesmo retorno.

Mesmo banco de dados.

Mesmo usuário.

Cada componente dispara sua própria requisição.

Isso aumenta o tempo de carregamento, desperdiça banda e sobrecarrega o servidor sem necessidade.


O problema fica ainda maior

Agora imagine outro cenário.

O usuário altera o nome de João para João Silva e salva.

Quem atualiza os componentes?

  • A listagem?
  • O painel lateral?
  • O modal?

Se você não fizer absolutamente nada, todos continuarão exibindo o nome antigo até que o usuário atualize a página.

Perceba que o problema deixou de ser buscar dados.

Nós já resolvemos isso utilizando Services.

Agora o desafio é outro:

Como administrar os dados vindos do servidor?


React nunca foi feito para resolver isso

Quando esse problema aparece, muita gente tenta resolvê-lo utilizando apenas as ferramentas nativas do React:

  • useState
  • useEffect
  • Context API

Essas ferramentas são excelentes...

...para estado local.

const [isModalOpen, setIsModalOpen] = useState(false);

Esse estado pertence exclusivamente ao componente.

Quando o componente desmonta, ele desaparece.

O React sabe exatamente quando ele muda.


Mas isto não é estado local

const users = await getUsersService();

A lista de usuários não pertence ao React.

Ela pertence ao servidor.

Ela pode mudar a qualquer momento.

Esse tipo de dado recebe um nome:

Estado Remoto (Server State).


Estado remoto possui características diferentes

O estado remoto possui propriedades que tornam seu gerenciamento muito mais complexo.

  • É assíncrono.
  • Pode demorar para chegar.
  • Pode estar desatualizado.
  • Pode ser alterado por outro usuário.
  • Precisa de cache.
  • Precisa ser sincronizado.
  • Precisa ser invalidado após mutações.

O React nunca tentou resolver esses problemas.

E nem deveria.

Foi exatamente para isso que nasceu o TanStack Query.


A nova peça da arquitetura

Nossa arquitetura praticamente não muda.

Ela apenas ganha uma nova camada.

Antes

Componentes
    ↓
Server Actions
    ↓
Services
    ↓
Axios
    ↓
API

Depois

Componentes
    ↓
TanStack Query
    ↓
Server Actions
    ↓
Services
    ↓
Axios
    ↓
API

Observe que:

  • continuamos usando Services;
  • continuamos usando Axios;
  • continuamos usando Server Actions.

A única diferença é que agora existe uma camada responsável por administrar os dados vindos da API.


Configurando o motor

A configuração é extremamente simples.

Criamos um QueryClient e envolvemos a aplicação com um QueryClientProvider.

Esse objeto será responsável por manter um cache compartilhado dentro do navegador.

Em outras palavras:

O QueryClient funciona como um pequeno banco de dados em memória.


O primeiro useQuery

Agora podemos abandonar completamente o padrão useEffect + useState.

"use client";

import { useQuery } from "@tanstack/react-query";
import { getUsersAction } from "@/actions/users/get-users-action";

export function UsersList() {
  const { data, isLoading, isError } = useQuery({
    queryKey: ["users"],
    queryFn: () => getUsersAction(),
  });

  if (isLoading) return <p>Carregando...</p>;
  if (isError) return <p>Falha ao carregar usuários.</p>;

  return (
    <ul>
      {data?.items.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

O que o TanStack Query faz?

Na prática, ele executa apenas três responsabilidades.

1. Buscar

Executa a função responsável pela requisição.

queryFn

2. Guardar

Armazena o resultado em cache.


3. Sincronizar

Notifica todos os componentes que dependem daquele dado.

Assim que o cache muda, toda a interface é atualizada automaticamente.

Sem eventos.

Sem Context API.

Sem Redux.

Sem dezenas de estados espalhados pela aplicação.


A chave de tudo: Query Keys

Na minha opinião, este é o conceito mais importante de toda a biblioteca.

queryKey: ["users"]

A Query Key funciona como o RG da requisição.

Ela identifica exatamente qual dado está armazenado no cache.

["users"]

Lista completa.

["users", 1]

Usuário de ID 1.

["users", { status: "active", page: 2 }]

Página 2 dos usuários ativos.

Sempre que qualquer valor da chave muda, o TanStack Query entende que se trata de outro recurso e cria um novo cache automaticamente.

Esse conceito será essencial quando trabalharmos com filtros, paginação e busca.


Agora a mágica acontece

Vamos voltar ao problema inicial.

Temos três componentes.

Todos pedem:

["users"]

O fluxo passa a ser o seguinte:

  1. O primeiro componente faz a requisição.
  2. O TanStack Query salva o resultado.
  3. O segundo componente pede os mesmos dados.
  4. O cache responde instantaneamente.
  5. Nenhuma nova requisição HTTP é realizada.

Todos os componentes compartilham exatamente o mesmo estado remoto.


staleTime

Outra funcionalidade extremamente importante é o staleTime.

staleTime: 1000 * 60 * 5

Nesse exemplo estamos dizendo:

Considere estes dados válidos durante cinco minutos.

Durante esse período:

  • não existem novas requisições;
  • a navegação fica instantânea;
  • o usuário praticamente não vê loading;
  • o servidor recebe muito menos chamadas.

É uma melhoria enorme de experiência e performance.


Conclusão

Curiosamente, nossos Services não precisaram mudar uma única linha.

Isso só foi possível porque a arquitetura foi construída respeitando responsabilidades bem definidas.

Cada camada continua fazendo exatamente aquilo que deveria fazer.

  • Os Services conversam com a API.
  • O Axios faz a comunicação HTTP.
  • O TanStack Query administra o estado remoto.
  • Os Componentes apenas exibem os dados.

Essa é a principal característica de uma arquitetura escalável:

Ela cresce adicionando novas camadas, e não reescrevendo as antigas.

No próximo artigo vamos resolver outro problema fundamental.

Buscar dados ficou extremamente simples.

Agora chegou a hora de enviá-los de volta ao servidor de forma segura utilizando React Hook Form, Zod e Server Actions.

Gostou deste artigo? Compartilhe com sua rede.