Estado global: Context API vs. Zustand
Prop drilling incomoda, mas a resposta nem sempre é Context — às vezes é uma lib pequena que não re-renderiza a árvore inteira.
Prop drilling — passar uma prop por 3, 4, 5 componentes só pra ela chegar lá no fundo, sem que nenhum componente no meio do caminho use ela — é chato, mas não é sempre um problema de verdade. Vira problema quando o valor muda com frequência, é usado em pontos bem distantes da árvore, ou quando adicionar mais um nível de aninhamento significa tocar em componentes que não têm nada a ver com aquele dado.
function App() {
const [usuario, setUsuario] = useState<Usuario | null>(null);
return <Layout usuario={usuario} />;
}
function Layout({ usuario }: { usuario: Usuario | null }) {
return <Header usuario={usuario} />; // Layout não usa "usuario", só repassa
}
function Header({ usuario }: { usuario: Usuario | null }) {
return <MenuDoUsuario usuario={usuario} />; // Header também só repassa
}Context API — built-in, mas com um custo real
O Context resolve o repasse: qualquer componente dentro do Provider lê o valor direto com useContext, sem prop nenhuma no meio do caminho.
const ThemeContext = createContext<"light" | "dark">("light");
function App() {
const [tema, setTema] = useState<"light" | "dark">("light");
return (
<ThemeContext.Provider value={tema}>
<Layout />
</ThemeContext.Provider>
);
}
function BotaoTema() {
const tema = useContext(ThemeContext); // sem prop nenhuma
return <button className={tema === "dark" ? "bg-slate-800" : "bg-white"}>Alternar tema</button>;
}O detalhe que pega muita gente de surpresa: quando o valor do Context muda, todo componente que consome aquele Context via useContext re-renderiza — mesmo que ele só use uma parte pequena do valor, ou que o valor seja um objeto grande com vários campos independentes. Pra um tema que muda raramente, isso não importa. Pra um estado que muda a cada tecla digitada, importa muito.
Zustand — estado que muda com frequência sem re-renderizar tudo
Zustand é uma lib pequena e sem boilerplate de Provider: você cria uma store fora da árvore de componentes, e cada componente escolhe, via um seletor, só a fatia do estado que precisa.
import { create } from "zustand";
type CarrinhoState = {
itens: { id: string; nome: string }[];
adicionar: (item: { id: string; nome: string }) => void;
};
export const useCarrinhoStore = create<CarrinhoState>((set) => ({
itens: [],
adicionar: (item) => set((state) => ({ itens: [...state.itens, item] })),
}));
// Um componente que só mostra a quantidade só re-renderiza quando "itens.length" muda:
function ContadorDoCarrinho() {
const quantidade = useCarrinhoStore((state) => state.itens.length);
return <span>{quantidade} itens</span>;
}Repare no seletor (state) => state.itens.length: o componente só re-renderiza quando aquele valor específico muda, não sempre que qualquer coisa na store muda. É essa diferença que faz Zustand escalar melhor pra estado que muda com frequência.
Quando usar qual
Context encaixa bem em valores que mudam raramente e são majoritariamente lidos — tema, idioma, o usuário autenticado. Zustand (ou lib parecida) encaixa melhor em estado que muda com frequência e onde componentes diferentes precisam de fatias diferentes dele — filtros de uma tabela, carrinho de compras, passos de um wizard.
Exercício
Reescreva o ContadorDoCarrinho acima como se ele lesse de um ThemeContext-like Context em vez de Zustand. Depois explique, em uma frase, por que essa versão re-renderiza mais vezes que a versão com seletor do Zustand.