Dominando React Query Avançado: Cache, Mutations, Optimistic Updates e Prefetch em Projetos Reais Já leu

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

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.

Referências


Artigos relacionados