<h2>Dominando o Cache do React Query</h2>
<p>O cache é o coração do React Query. A biblioteca gerencia automaticamente quando buscar dados, quando reutilizá-los e quando invalidá-los. Entender sua estratégia é fundamental para construir aplicações eficientes.</p>
<p>Por padrão, o React Query mantém dados em cache por 5 minutos (staleTime padrão é 0, mas o cacheTime é 5 minutos). Dados "stale" ainda são retornados instantaneamente, mas uma nova busca é disparada em background. Isso evita requisições desnecessárias enquanto mantém dados frescos.</p>
<pre><code class="language-javascript">import { useQuery } from '@tanstack/react-query';
const fetchUsers = async () => {
const res = await fetch('/api/users');
return res.json();
};
export function UserList() {
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 1000 60 5, // 5 minutos
gcTime: 1000 60 10, // Remove do cache após 10 min de inatividade
});
if (isLoading) return <div>Carregando...</div>;
return <ul>{data?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}</code></pre>
<p>Para estratégias mais avançadas, use queryKey como array com identificadores. Isso permite invalidar caches granulares e fazer prefetch eficiente de dados relacionados.</p>
<h2>Mutations: Alterando Dados com Controle Total</h2>
<p>Mutations são operações que modificam estado no servidor. Diferente de queries, elas não são automáticas e você tem controle explícito sobre quando executá-las.</p>
<p>O <code>useMutation</code> hook retorna uma função mutate que você invoca quando necessário. Ele fornece callbacks úteis: onSuccess (após sucesso), onError (se falhar) e onMutate (antes de executar, essencial para otimistic updates).</p>
<pre><code class="language-javascript">import { useMutation, useQueryClient } from '@tanstack/react-query';
const createUser = async (newUser) => {
const res = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newUser),
});
return res.json();
};
export function CreateUserForm() {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createUser,
onSuccess: (data) => {
// Invalida cache e refetch automático
queryClient.invalidateQueries({ queryKey: ['users'] });
},
onError: (error) => {
console.error('Erro ao criar usuário:', error);
},
});
const handleSubmit = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
mutation.mutate({ name: formData.get('name'), email: formData.get('email') });
};
return (
<form onSubmit={handleSubmit}>
<input name="name" placeholder="Nome" required />
<input name="email" placeholder="Email" required />
<button disabled={mutation.isPending}>
{mutation.isPending ? 'Salvando...' : 'Criar'}
</button>
{mutation.isError && <p>{mutation.error.message}</p>}
</form>
);
}</code></pre>
<h2>Optimistic Updates: UX Instantânea</h2>
<p>Optimistic updates atualizam a UI imediatamente, assumindo que a operação terá sucesso. Se falhar, você reverte. Isso cria uma experiência fluida sem esperar pela resposta do servidor.</p>
<p>A chave está no <code>onMutate</code>: você acessa o cache atual, atualiza-o otimistamente e retorna um contexto para reverter se necessário. Use <code>setQueryData</code> para manipular o cache manualmente.</p>
<pre><code class="language-javascript">import { useMutation, useQueryClient } from '@tanstack/react-query';
const updateUserStatus = async (userId, status) => {
const res = await fetch(/api/users/${userId}, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status }),
});
return res.json();
};
export function UserStatusToggle({ userId, currentStatus }) {
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: ({ id, status }) => updateUserStatus(id, status),
onMutate: async ({ id, status }) => {
// Cancela requisições em background
await queryClient.cancelQueries({ queryKey: ['users'] });
// Salva estado anterior
const previousUsers = queryClient.getQueryData(['users']);
// Atualiza cache otimistamente
queryClient.setQueryData(['users'], (old) =>
old?.map(u => u.id === id ? { ...u, status } : u)
);
return { previousUsers }; // Contexto para rollback
},
onError: (err, variables, context) => {
// Reverte em caso de erro
if (context?.previousUsers) {
queryClient.setQueryData(['users'], context.previousUsers);
}
},
onSuccess: () => {
// Sincroniza com servidor após sucesso (opcional)
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
const newStatus = currentStatus === 'active' ? 'inactive' : 'active';
return (
<button
onClick={() => mutation.mutate({ id: userId, status: newStatus })}
disabled={mutation.isPending}
>
{mutation.isPending ? 'Atualizando...' : Marcar como ${newStatus}}
</button>
);
}</code></pre>
<h2>Prefetch e Estratégias de Carregamento</h2>
<p>Prefetch carrega dados antes do usuário realmente precisar deles. É perfeito para navegar entre páginas ou expandir listas. Use <code>prefetchQuery</code> para disparar requisições em background sem bloquear a UI.</p>
<p>Uma estratégia comum é prefetch ao passar o mouse sobre um link, ou prefetch dados relacionados assim que a página carrega. Isso reduz waterfalls de requisições e melhora drasticamente a percepção de velocidade.</p>
<pre><code class="language-javascript">import { useQuery, usePrefetchQuery } from '@tanstack/react-query';
const fetchUserDetails = async (userId) => {
const res = await fetch(/api/users/${userId});
return res.json();
};
export function UserPreview({ userId }) {
const prefetch = usePrefetchQuery();
const handleMouseEnter = () => {
// Dispara prefetch ao passar o mouse
prefetch({
queryKey: ['userDetails', userId],
queryFn: () => fetchUserDetails(userId),
staleTime: 1000 * 60, // 1 minuto
});
};
return (
<div onMouseEnter={handleMouseEnter}>
<a href={/users/${userId}}>Ver Perfil</a>
</div>
);
}
export function UserDetails({ userId }) {
// Dados já podem estar em cache pelo prefetch
const { data, isLoading } = useQuery({
queryKey: ['userDetails', userId],
queryFn: () => fetchUserDetails(userId),
});
if (isLoading) return <div>Carregando...</div>;
return <div>{data?.name} - {data?.email}</div>;
}</code></pre>
<p>Outra abordagem é prefetch em paralelo quando uma página carrega. Isso é útil em dashboards onde você quer ter múltiplos dados prontos simultaneamente.</p>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>cache eficiente</strong> com staleTime e gcTime evita requisições desnecessárias. <strong>Mutations com callbacks</strong> oferecem controle fino sobre operações de escrita. <strong>Optimistic updates</strong> criam experiências instantâneas ao atualizar o cache antes da resposta do servidor chegar, revertendo se houver erro. <strong>Prefetch</strong> reduz latência ao carregar dados proativamente.</p>
<p>Domine esses conceitos e você construirá interfaces altamente responsivas, com gerenciamento de estado servidor simplificado e experiência de usuário excepcional.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://tanstack.com/query/latest" target="_blank" rel="noopener noreferrer">React Query Official Docs</a></li>
<li><a href="https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults" target="_blank" rel="noopener noreferrer">Caching Strategies Guide</a></li>
<li><a href="https://tkdodo.eu/blog/react-query" target="_blank" rel="noopener noreferrer">TkDodo's React Query Course</a></li>
<li><a href="https://tanstack.com/query/latest/docs/framework/react/guides/optimistic-updates" target="_blank" rel="noopener noreferrer">Optimistic Updates Pattern</a></li>
<li><a href="https://github.com/tanstack/query" target="_blank" rel="noopener noreferrer">React Query GitHub Repository</a></li>
</ul>