<h2>Entendendo o Cache e sua Importância no React Query</h2>
<p>O cache é o coração do React Query. Diferentemente de outras bibliotecas que apenas gerenciam estado, o React Query mantém uma cópia dos dados já fetched no navegador, evitando requisições desnecessárias. Quando você faz uma query pela primeira vez, a resposta é armazenada em memória com uma chave única. Na próxima vez que essa query for necessária, o React Query retorna os dados do cache instantaneamente, sem fazer uma nova requisição HTTP.</p>
<p>Para entender isso na prática, vamos ver como o cache funciona por padrão. Quando você cria uma query com <code>useQuery</code>, ela passa por diferentes estados: <code>isLoading</code> (primeira requisição), <code>isFetching</code> (busca em background), e finalmente retorna dados do cache. O cache também tem um conceito de "stale" (obsoleto), que é quando os dados precisam ser revalidados com o servidor.</p>
<pre><code class="language-typescript">import { useQuery } from '@tanstack/react-query';
const UserProfile = ({ userId }: { userId: string }) => {
const { data, isLoading, isFetching } = useQuery({
queryKey: ['user', userId],
queryFn: async () => {
const response = await fetch(/api/users/${userId});
return response.json();
},
staleTime: 5 60 1000, // 5 minutos
});
if (isLoading) return <div>Carregando...</div>;
return (
<div>
<h1>{data.name}</h1>
{isFetching && <span>(atualizando...)</span>}
</div>
);
};</code></pre>
<p>Neste exemplo, após os primeiros 5 minutos (<code>staleTime</code>), os dados são considerados obsoletos. Mas observe: os dados ainda estão no cache, apenas marcados como stale. Se o usuário navegar para outra página e voltar dentro da janela de <code>cacheTime</code>, os dados serão mostrados imediatamente enquanto uma nova requisição é feita em background.</p>
<h2>Dominando Stale Time e Cache Time</h2>
<p>Stale Time e Cache Time são dois conceitos distintos que precisam ser bem compreendidos. <strong>Stale Time</strong> define quanto tempo os dados são considerados "frescos". Enquanto os dados não estão stale, nenhuma requisição é feita em background. <strong>Cache Time</strong> (renomeado para <code>gcTime</code> no TanStack Query v5) define por quanto tempo os dados permanecem na memória após não serem mais utilizados.</p>
<p>A configuração padrão do React Query é <code>staleTime: 0</code> e <code>gcTime: 5 <em> 60 </em> 1000</code> (5 minutos). Isso significa que os dados são imediatamente considerados obsoletos, mas permanecem na memória por 5 minutos após o último acesso. Se um usuário navegar para a página de perfil, depois para home, e retornar ao perfil dentro de 5 minutos, os dados serão mostrados do cache e uma requisição silenciosa será feita em background.</p>
<pre><code class="language-typescript">import { useQuery, useQueryClient } from '@tanstack/react-query';
const ProductList = () => {
const queryClient = useQueryClient();
const { data: products } = useQuery({
queryKey: ['products'],
queryFn: async () => {
const response = await fetch('/api/products');
return response.json();
},
staleTime: 10 60 1000, // 10 minutos - dados ficam frescos por 10 min
gcTime: 15 60 1000, // 15 minutos - cache permanece por 15 min
});
const handleRefreshManual = () => {
// Força revalidação imediata
queryClient.invalidateQueries({ queryKey: ['products'] });
};
return (
<div>
<button onClick={handleRefreshManual}>Atualizar dados</button>
{/ renderizar produtos /}
</div>
);
};</code></pre>
<p>A estratégia correta depende do seu caso de uso. Para dados que mudam frequentemente (como notificações ou preços), use um <code>staleTime</code> menor. Para dados mais estáticos (como informações de um artigo), use um <code>staleTime</code> maior. O <code>gcTime</code> deve ser sempre maior que o <code>staleTime</code>, caso contrário o cache será limpo antes dos dados serem considerados stale.</p>
<h3>Invalidação Manual e Revalidação Inteligente</h3>
<p>Às vezes, você sabe que os dados ficaram obsoletos e precisa invalidá-los manualmente. O <code>queryClient.invalidateQueries()</code> marca queries como stale, forçando uma revalidação quando o componente tentar acessá-las. Isso é diferente de deletar o cache: os dados continuam disponíveis enquanto a nova requisição é feita.</p>
<pre><code class="language-typescript">import { useMutation, useQueryClient } from '@tanstack/react-query';
const CreatePost = () => {
const queryClient = useQueryClient();
const createPostMutation = useMutation({
mutationFn: async (newPost: { title: string; content: string }) => {
const response = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newPost),
});
return response.json();
},
onSuccess: (data) => {
// Invalida a lista de posts para que seja recarregada
queryClient.invalidateQueries({ queryKey: ['posts'] });
// Ou atualiza o cache diretamente (mais eficiente)
queryClient.setQueryData(['posts'], (oldData: any) => [
...oldData,
data,
]);
},
});
return (
<button onClick={() => createPostMutation.mutate({ title: '', content: '' })}>
Criar Post
</button>
);
};</code></pre>
<h2>Prefetch: Carregando Dados Antes da Necessidade</h2>
<p>Prefetch é uma técnica poderosa para melhorar a experiência do usuário. Ao invés de esperar o usuário clicar em um link ou navegar para uma página, você antecipa quais dados serão necessários e os carrega em background. Isso é particularmente útil em paginação, listagens onde o usuário provavelmente vai clicar no próximo item, ou em previsão de navegação.</p>
<p>O React Query fornece o <code>queryClient.prefetchQuery()</code> que faz uma requisição sem renderizar dados, apenas armazenando no cache. Se um componente depois usar <code>useQuery</code> com a mesma chave, os dados já estarão disponíveis.</p>
<pre><code class="language-typescript">import { useQueryClient } from '@tanstack/react-query';
import { useEffect } from 'react';
const ProductListWithPrefetch = ({ currentPage }: { currentPage: number }) => {
const queryClient = useQueryClient();
// Prefetch da próxima página quando a página atual carregar
useEffect(() => {
queryClient.prefetchQuery({
queryKey: ['products', currentPage + 1],
queryFn: async () => {
const response = await fetch(/api/products?page=${currentPage + 1});
return response.json();
},
staleTime: 5 60 1000,
});
}, [currentPage, queryClient]);
// Query da página atual
const { data: products } = useQuery({
queryKey: ['products', currentPage],
queryFn: async () => {
const response = await fetch(/api/products?page=${currentPage});
return response.json();
},
});
return (
<div>
{/ renderizar produtos /}
</div>
);
};</code></pre>
<p>Uma implementação mais sofisticada é prefetch em hover. Quando o usuário passa o mouse sobre um link, você já carrega os dados que ele provavelmente vai ver:</p>
<pre><code class="language-typescript">import { useQueryClient } from '@tanstack/react-query';
const UserLink = ({ userId }: { userId: string }) => {
const queryClient = useQueryClient();
const handleMouseEnter = () => {
queryClient.prefetchQuery({
queryKey: ['user', userId],
queryFn: async () => {
const response = await fetch(/api/users/${userId});
return response.json();
},
staleTime: 10 60 1000,
});
};
return (
<a
href={/users/${userId}}
onMouseEnter={handleMouseEnter}
>
Ver Perfil
</a>
);
};</code></pre>
<h2>Optimistic UI: Atualizações Antes da Confirmação do Servidor</h2>
<p>Optimistic UI é quando você atualiza a interface do usuário imediatamente, antes de confirmar com o servidor que a operação foi bem-sucedida. Isso cria uma experiência mais rápida e responsiva. Se algo der errado, você reverte a mudança.</p>
<p>O React Query facilitou isso com as callbacks <code>onMutate</code>, <code>onSuccess</code> e <code>onError</code> do <code>useMutation</code>. A estratégia típica é: quando o usuário submete uma ação, você atualiza o cache otimisticamente, faz a requisição, e se falhar, reverte.</p>
<pre><code class="language-typescript">import { useMutation, useQueryClient } from '@tanstack/react-query';
interface Post {
id: string;
title: string;
content: string;
}
const EditPost = ({ postId, initialPost }: { postId: string; initialPost: Post }) => {
const queryClient = useQueryClient();
const editPostMutation = useMutation({
mutationFn: async (updatedPost: Post) => {
const response = await fetch(/api/posts/${postId}, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updatedPost),
});
if (!response.ok) {
throw new Error('Falha ao atualizar post');
}
return response.json();
},
onMutate: async (newPost) => {
// Cancela qualquer query em andamento para evitar sobrescrita
await queryClient.cancelQueries({ queryKey: ['post', postId] });
// Salva os dados antigos em caso de rollback
const previousPost = queryClient.getQueryData<Post>(['post', postId]);
// Atualiza o cache otimisticamente
queryClient.setQueryData(['post', postId], newPost);
// Retorna o contexto para usar em onError
return { previousPost };
},
onSuccess: (data) => {
// Sucesso confirmado - dados já estão corretos no cache
console.log('Post atualizado com sucesso:', data);
},
onError: (error, variables, context) => {
// Se algo der errado, reverte para os dados antigos
if (context?.previousPost) {
queryClient.setQueryData(['post', postId], context.previousPost);
}
console.error('Erro ao atualizar post:', error);
},
});
const handleSave = (updatedPost: Post) => {
editPostMutation.mutate(updatedPost);
};
return (
<div>
{editPostMutation.isPending && <span>Salvando...</span>}
{editPostMutation.isError && <span>Erro ao salvar</span>}
{/ Formulário aqui /}
</div>
);
};</code></pre>
<p>Um exemplo ainda mais realista é quando você está atualizando uma lista e precisa refletir a mudança imediatamente:</p>
<pre><code class="language-typescript">import { useMutation, useQueryClient } from '@tanstack/react-query';
interface Task {
id: string;
title: string;
completed: boolean;
}
const TaskToggle = ({ task }: { task: Task }) => {
const queryClient = useQueryClient();
const toggleTaskMutation = useMutation({
mutationFn: async (completed: boolean) => {
const response = await fetch(/api/tasks/${task.id}, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed }),
});
return response.json();
},
onMutate: async (newCompleted) => {
// Cancela requisições simultâneas
await queryClient.cancelQueries({ queryKey: ['tasks'] });
// Salva estado anterior
const previousTasks = queryClient.getQueryData<Task[]>(['tasks']);
// Atualiza cache otimisticamente
queryClient.setQueryData(['tasks'], (oldTasks: Task[]) =>
oldTasks.map((t) =>
t.id === task.id ? { ...t, completed: newCompleted } : t
)
);
return { previousTasks };
},
onError: (error, variables, context) => {
// Reverte em caso de erro
if (context?.previousTasks) {
queryClient.setQueryData(['tasks'], context.previousTasks);
}
},
});
return (
<input
type="checkbox"
checked={task.completed}
onChange={(e) => toggleTaskMutation.mutate(e.target.checked)}
disabled={toggleTaskMutation.isPending}
/>
);
};</code></pre>
<p>A chave do Optimistic UI é sempre ter um plano de reversão. Se você não conseguir reverter facilmente, não use essa técnica, pois pode deixar o usuário com dados inconsistentes.</p>
<h2>Conclusão</h2>
<p>Os três pilares avançados do React Query que dominamos neste artigo — <strong>Cache inteligente com Stale Time</strong>, <strong>Prefetch para antecipar dados</strong>, e <strong>Optimistic UI para feedback imediato</strong> — transformam a experiência do usuário de uma forma que é invisível mas profundamente sentida. Você não apenas reduz requisições desnecessárias, mas cria interfaces que parecem instantâneas mesmo com conexões lentas.</p>
<p>A regra prática que deve guiar suas decisões: configure um <code>staleTime</code> apropriado para quantas vezes seus dados mudam (dados estáticos = staleTime alto), use <code>prefetchQuery()</code> quando puder antecipar onde o usuário vai clicar, e implemente Optimistic UI apenas em ações onde você pode reverter com segurança. O React Query fornece todos os mecanismos; a verdadeira maestria está em saber quando e como usá-los.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://tanstack.com/query/latest" target="_blank" rel="noopener noreferrer">TanStack Query Official Documentation</a></li>
<li><a href="https://tanstack.com/query/latest/docs/react/installation" target="_blank" rel="noopener noreferrer">React Query v4 to v5 Migration Guide</a></li>
<li><a href="https://tanstack.com/query/latest/docs/react/important-defaults" target="_blank" rel="noopener noreferrer">Important Defaults - Query Behavior</a></li>
<li><a href="https://tanstack.com/query/latest/docs/react/guides/important-defaults#stale-time" target="_blank" rel="noopener noreferrer">Optimistic Updates Pattern</a></li>
<li><a href="https://tkdodo.eu/blog/practical-react-query" target="_blank" rel="noopener noreferrer">Tkdodo's React Query Blog - Practical React Query</a></li>
</ul>
<p><!-- FIM --></p>