Spike: Gerência de estado global (React 19 + Vite)
Projeto acme-dashboard (React 19.1, Vite 6, TypeScript 5.6, pnpm). Hoje o estado vive em prop drilling e alguns useContext; conforme o dashboard cresceu, re-renders e o acoplamento entre telas viraram atrito. Este spike compara três abordagens para um store global, com foco na stack real.
Objetivo e critérios de decisão
- Bundle: peso gzip somado ao app (orçamento apertado no first load).
- Curva e DX: quão rápido o time entrega sem boilerplate.
- Modelo mental: store único vs. átomos; previsibilidade dos re-renders.
- Maturidade: adoção, manutenção ativa, ecossistema de middleware.
- Compatibilidade: React 19 + Vite + TS estrito, sem gambiarra.
Opções analisadas
Opção 1 — Zustand
Store como hook, fora da árvore React; seletores controlam o re-render.
import { create } from "zustand";
type Store = { count: number; inc: () => void };
export const useStore = create<Store>((set) => ({
count: 0,
inc: () => set((s) => ({ count: s.count + 1 })),
}));
Prós: API mínima, sem provider, seletores enxutos, ótimo TS. Contras: disciplina de seletores fica com o time; sem opinião de arquitetura.
Opção 2 — Jotai
Estado como átomos compostos; re-render só de quem lê o átomo.
import { atom, useAtom } from "jotai";
const countAtom = atom(0);
export const useCount = () => useAtom(countAtom);
Prós: granularidade natural, excelente para estado derivado. Contras: muitos átomos espalham a lógica; curva conceitual maior.
Opção 3 — Redux Toolkit
Store previsível com slices, devtools e middleware padronizados.
Prós: convenção forte, devtools e RTK Query maduros, escala em time grande. Contras: mais boilerplate e maior peso; excessivo para um dashboard médio.
Comparação
| Critério | Zustand | Jotai | Redux Toolkit |
|---|---|---|---|
| Bundle (gzip) | ~1,1 kB | ~3,5 kB | ~13 kB (+react-redux) |
| Downloads/sem. (npm) | ~5,5 M | ~1,6 M | ~4,8 M |
| Boilerplate | mínimo | baixo | médio-alto |
| Modelo mental | store único | átomos | store + slices |
| Licença | MIT | MIT | MIT |
Números coletados em 2026-07-12 (npm registry, npm downloads, bundlephobia). Confira antes de decidir. O ecossistema muda.
Recomendação
Adote Zustand. Para um SPA React 19 de tamanho médio, ele entrega o melhor equilíbrio: bundle irrisório, zero provider e um modelo mental que o time absorve numa tarde. O atrito atual (prop drilling e re-renders) resolve-se com um store + seletores, sem trocar a arquitetura do app.
Reconsidere se: o estado for majoritariamente derivado e granular (Jotai brilha aí), ou se o time já for grande e padronizado em Redux com uso pesado de RTK Query, aí a convenção do Redux Toolkit paga o peso extra.
Plano de prova de conceito
pnpm add zustande criarsrc/store/ui.tscom um slice real (ex.: filtros do dashboard). Timebox: 2h- Migrar UMA tela do prop drilling para o store, medindo re-renders com o React DevTools Profiler. Timebox: 3h
- Adicionar persistência (
persist) só do que precisa sobreviver a reload. Timebox: 1h - Revisão: comparar bundle antes/depois e validar seletores. Timebox: 1h
Riscos e mitigação
| Risco | Mitigação |
|---|---|
| Seletores mal feitos re-renderizam demais | Lint de seletor + revisão no PR da PoC |
| Store vira "balde global" | Slices por domínio; nada de lógica de servidor no store |
| Estado de servidor no lugar errado | Manter data-fetching em React Query, store só para UI |
Critérios de aceite do spike
- Uma tela migrada com re-renders medidos antes/depois
- Diferença de bundle documentada
- Convenção de slices escrita no README do time
- Decisão registrada (este documento) no repositório