Gerenciamento de estado global com dependências cruzadas
Abstract
Abaixo um exemplo mostrando uma limitação do SWR em cenários complexos de gerenciamento de estado. A ideia é mostrar uma situação em que o SWR sozinho pode não ser suficiente, exigindo bibliotecas complementares como Redux, Zustand ou Context API para lidar com a complexidade.
Imagine uma aplicação de comércio eletrônico onde você tem uma página de carrinho de compras que precisa lidar com os seguintes requisitos:
- Dados Remotos: Buscar a lista de itens disponíveis em uma API (ex.: 
/api/produtos). - Estado Local: Manter um estado do carrinho (itens adicionados pelo usuário) que não está no servidor até o checkout.
 - Dependências Cruzadas: Atualizar automaticamente o preço total do carrinho quando:
- O usuário adiciona ou remove itens.
 - A API retorna uma mudança nos preços dos produtos (ex.: uma promoção).
 
 - Sincronização: Garantir que o estado local (carrinho) e os dados remotos (preços) estejam sempre sincronizados.
 
Tentativa com SWR
Aqui está como você poderia tentar implementar isso usando apenas SWR:
"use client";
import useSWR from "swr";
 
const fetcher = (url) => fetch(url).then((res) => res.json());
 
export default function Carrinho() {
  // Busca os dados dos produtos da API
  const { data: produtos, error, mutate } = useSWR("/api/produtos", fetcher);
 
  // Tentativa de gerenciar o carrinho localmente com useState
  const [carrinho, setCarrinho] = React.useState([]);
 
  // Função para adicionar ao carrinho
  const adicionarAoCarrinho = (produtoId) => {
    const produto = produtos?.find((p) => p.id === produtoId);
    if (produto) {
      setCarrinho((prev) => [...prev, produto]);
    }
  };
 
  // Calcular o preço total
  const precoTotal = carrinho.reduce((total, item) => total + item.preco, 0);
 
  if (error) return <div>Erro ao carregar produtos</div>;
  if (!produtos) return <div>Carregando...</div>;
 
  return (
    <div>
      <h1>Carrinho</h1>
      <ul>
        {carrinho.map((item) => (
          <li key={item.id}>{item.nome} - R$ {item.preco}</li>
        ))}
      </ul>
      <p>Total: R$ {precoTotal}</p>
      <button onClick={() => adicionarAoCarrinho(produtos[0].id)}>
        Adicionar primeiro produto
      </button>
    </div>
  );
}Problemas com SWR nesse cenário
Estado Local vs Remoto
O SWR é ótimo para buscar e atualizar os produtos da API, mas não gerencia o estado local do carrinho. O useState aqui é independente do SWR, o que cria uma desconexão entre os dados remotos e o estado local.
Se os preços dos produtos mudarem na API (ex.: promoção), o carrinho não reflete isso automaticamente porque ele armazena cópias antigas dos itens.
Sincronização Manual
Para sincronizar o carrinho com os novos preços, você precisaria chamar mutate para atualizar os produtos e então manualmente recalcular o carrinho. Isso é trabalhoso e propenso a erros.
Exemplo:
const atualizarCarrinho = () => {
  mutate("/api/produtos", async () => {
    const novosProdutos = await fetcher("/api/produtos");
    setCarrinho((prev) =>
      prev.map((item) => ({
   	 ...item,
   	 preco: novosProdutos.find((p) => p.id === item.id).preco,
      }))
    );
    return novosProdutos;
  });
};Dependências Cruzadas
Se outra parte da aplicação (ex.: uma página de recomendações) também modificar o carrinho, o SWR não tem uma forma nativa de manter um estado global consistente entre múltiplos componentes ou páginas.
Escalabilidade
À medida que a aplicação cresce (ex.: filtros no carrinho, cupons de desconto, etc.), gerenciar todas essas interações apenas com SWR e useState fica caótico.
Solução com Biblioteca Complementar
Para resolver isso, você pode combinar o SWR com uma biblioteca de gerenciamento de estado global, como Zustand, que é leve e simples. O SWR ficaria responsável pelo fetching de dados remotos, enquanto o Zustand gerenciaria o estado do carrinho e as dependências cruzadas.
Exemplo com Zustand
Store com Zustand
// store/carrinhoStore.js
import { create } from "zustand";
 
export const useCarrinhoStore = create((set) => ({
  carrinho: [],
  adicionarAoCarrinho: (produto) =>
    set((state) => ({ carrinho: [...state.carrinho, produto] })),
  limparCarrinho: () => set({ carrinho: [] }),
}));Componente Revisado
"use client";
import useSWR from "swr";
import { useCarrinhoStore } from "@/store/carrinhoStore";
 
const fetcher = (url) => fetch(url).then((res) => res.json());
 
export default function Carrinho() {
  const { data: produtos, error } = useSWR("/api/produtos", fetcher);
  const { carrinho, adicionarAoCarrinho } = useCarrinhoStore();
 
  // Calcula o preço total com base nos dados mais recentes
  const precoTotal = carrinho.reduce((total, item) => {
    const produtoAtual = produtos?.find((p) => p.id === item.id);
    return total + (produtoAtual ? produtoAtual.preco : item.preco);
  }, 0);
 
  if (error) return <div>Erro ao carregar produtos</div>;
  if (!produtos) return <div>Carregando...</div>;
 
  return (
    <div>
      <h1>Carrinho</h1>
      <ul>
        {carrinho.map((item) => (
          <li key={item.id}>
            {item.nome} - R${" "}
            {produtos.find((p) => p.id === item.id)?.preco || item.preco}
          </li>
        ))}
      </ul>
      <p>Total: R$ {precoTotal}</p>
      <button onClick={() => adicionarAoCarrinho(produtos[0])}>
        Adicionar primeiro produto
      </button>
    </div>
  );
}Vantagens dessa Abordagem
- Separação de Responsabilidades: SWR cuida do fetching e caching dos produtos, enquanto Zustand gerencia o estado global do carrinho.
 - Sincronização Automática: O preço total reflete os dados mais recentes da API sem manipulação manual complexa.
 - Escalabilidade: O Zustand pode ser usado em outras páginas ou componentes sem duplicação de lógica.
 
Por que Isso Mostra a Limitação do SWR?
O SWR é projetado para fetching de dados e caching, não para gerenciamento de estado global ou dependências complexas entre dados locais e remotos.
Quando você precisa de algo além de buscar e revalidar dados (como manter um carrinho sincronizado com preços dinâmicos ou compartilhar estado entre componentes), ele começa a exigir soluções manuais ou bibliotecas complementares.