Lembrete: Context
useContext resolve o prop drilling: em vez de passar uma prop por dez
níveis, um Provider oferece um valor e qualquer descendente o lê. É
perfeito para dados que mudam pouco: o tema (claro/escuro), o idioma, o
usuário autenticado.
Onde o Context fica aquém
O Context não foi projetado como gerenciador de estado de alto desempenho. Sua limitação principal são os re-renders:
Quando o valor de um
Providermuda, todos os componentes que consomem esse contexto são re-renderizados, mesmo que só lhes interesse uma parte do valor.
Se você coloca em um único contexto um objeto grande ({ usuario, carrinho, ajustes, notificacoes }) e muda uma propriedade, todos os consumidores
são repintados. O Context não tem seletores: você não pode se inscrever só em
carrinho.total e ignorar o resto. Em um app com estado que muda com frequência,
isso se traduz em renders desnecessários e a solução na mão (dividir em muitos
contextos, memoizar) fica complicada.
Aí entram as bibliotecas de estado global, cuja grande vantagem são os seletores: cada componente se inscreve só no pedaço de estado que usa.
Zustand
Zustand é minimalista. Você cria um store com create passando uma função
que define estado e ações:
import { create } from "zustand";
const useContador = create((set) => ({
cuenta: 0,
incrementar: () => set((s) => ({ cuenta: s.cuenta + 1 })),
reset: () => set({ cuenta: 0 }),
}));
O próprio store é um hook. Você o usa com um seletor para ler só o que precisa:
function Placar() {
// Re-renderiza SÓ se 'cuenta' mudar
const cuenta = useContador((s) => s.cuenta);
return <p>Contagem: {cuenta}</p>;
}
function Botao() {
// Este só lê a ação: não re-renderiza quando 'cuenta' muda
const incrementar = useContador((s) => s.incrementar);
return <button onClick={incrementar}>+1</button>;
}
Não precisa de Provider envolvendo o app, não há boilerplate e o seletor
(s) => s.cuenta evita os re-renders que o Context sofreria.
Redux Toolkit
Redux é o clássico do estado global previsível: um único store, o estado só muda despachando ações que passam por reducers puros. Redux Toolkit (RTK) é a forma oficial e moderna de usá-lo, e reduz muitíssimo o código repetitivo graças aos slices:
import { configureStore, createSlice } from "@reduxjs/toolkit";
const contadorSlice = createSlice({
name: "contador",
initialState: { cuenta: 0 },
reducers: {
incrementar: (state) => { state.cuenta += 1; }, // Immer: você "muta" um rascunho
reset: (state) => { state.cuenta = 0; },
},
});
export const { incrementar, reset } = contadorSlice.actions;
export const store = configureStore({
reducer: { contador: contadorSlice.reducer },
});
Nos componentes você lê com useSelector (com seu seletor, como no Zustand)
e despacha ações com useDispatch:
import { useSelector, useDispatch } from "react-redux";
function Placar() {
const cuenta = useSelector((s) => s.contador.cuenta);
const dispatch = useDispatch();
return (
<button onClick={() => dispatch(incrementar())}>
Contagem: {cuenta}
</button>
);
}
Dentro de um reducer do RTK parece que você muta o estado
(state.cuenta += 1), mas por baixo ele usa Immer para produzir um novo
estado imutável de forma segura.
Qual escolher?
- Context: dados globais que mudam pouco (tema, idioma, sessão). Vem com o React, sem dependências.
- Zustand: estado de cliente que muda com frequência, com pouco boilerplate e seletores. Excelente meio-termo.
- Redux Toolkit: apps grandes que se beneficiam de um fluxo muito estruturado, DevTools potentes, middlewares e convenções de equipe.
E lembre-se: para dados do servidor, nenhum destes; use TanStack Query ou SWR. O estado global é para o estado do cliente.
(Zustand e Redux são instalados como dependências; aqui os vemos de forma conceitual.)