Dominando o Cache do React Query
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.
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.
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>;
}
Para estratégias mais avançadas, use queryKey como array com identificadores. Isso permite invalidar caches granulares e fazer prefetch eficiente de dados relacionados.
Mutations: Alterando Dados com Controle Total
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.
O useMutation 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).
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>
);
}
Optimistic Updates: UX Instantânea
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.
A chave está no onMutate: você acessa o cache atual, atualiza-o otimistamente e retorna um contexto para reverter se necessário. Use setQueryData para manipular o cache manualmente.
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>
);
}
Prefetch e Estratégias de Carregamento
Prefetch carrega dados antes do usuário realmente precisar deles. É perfeito para navegar entre páginas ou expandir listas. Use prefetchQuery para disparar requisições em background sem bloquear a UI.
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.
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>;
}
Outra abordagem é prefetch em paralelo quando uma página carrega. Isso é útil em dashboards onde você quer ter múltiplos dados prontos simultaneamente.
Conclusão
Você aprendeu que cache eficiente com staleTime e gcTime evita requisições desnecessárias. Mutations com callbacks oferecem controle fino sobre operações de escrita. Optimistic updates criam experiências instantâneas ao atualizar o cache antes da resposta do servidor chegar, revertendo se houver erro. Prefetch reduz latência ao carregar dados proativamente.
Domine esses conceitos e você construirá interfaces altamente responsivas, com gerenciamento de estado servidor simplificado e experiência de usuário excepcional.