Mobile
Estado Global no React Native: Context vs Zustand vs Redux
20 de maio de 2026·Paulo Pereira
O Problema do Estado Global
Passar props através de 5 componentes para chegar onde precisa (prop drilling) é o sinal de que você precisa de estado global. No React Native, as opções são:
- Context API — nativa do React, sem dependência
- Zustand — leve, simples, performático
- Redux Toolkit — verboso, mas poderoso para apps complexos
- Jotai/Recoil — estado atômico
Context API: Quando Usar
Ótimo para estado que muda raramente: tema, usuário autenticado, idioma.
// contexts/AuthContext.tsx
import { createContext, useContext, useState, useCallback, ReactNode } from 'react';
type Usuario = { id: number; nome: string; email: string };
type AuthContextType = {
usuario: Usuario | null;
autenticar: (email: string, senha: string) => Promise<void>;
sair: () => void;
};
const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [usuario, setUsuario] = useState<Usuario | null>(null);
const autenticar = useCallback(async (email: string, senha: string) => {
const resposta = await api.post('/auth/login', { email, senha });
setUsuario(resposta.data.usuario);
}, []);
const sair = useCallback(() => {
setUsuario(null);
}, []);
return (
<AuthContext.Provider value={{ usuario, autenticar, sair }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth deve ser usado dentro de AuthProvider');
return ctx;
}Problema do Context: qualquer mudança no valor re-renderiza TODOS os consumidores. Para estado que muda frequentemente (carrinho, contador, preferências), use Zustand.
Zustand: A Escolha Para a Maioria dos Apps
npm install zustand// stores/carrinhoStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';
type Produto = { id: number; nome: string; preco: number };
type ItemCarrinho = Produto & { quantidade: number };
type CarrinhoState = {
itens: ItemCarrinho[];
totalItens: number;
totalPreco: number;
adicionarItem: (produto: Produto) => void;
removerItem: (id: number) => void;
atualizarQuantidade: (id: number, quantidade: number) => void;
limpar: () => void;
};
export const useCarrinhoStore = create<CarrinhoState>()(
persist(
(set, get) => ({
itens: [],
totalItens: 0,
totalPreco: 0,
adicionarItem: (produto) =>
set((state) => {
const existente = state.itens.find((i) => i.id === produto.id);
const itens = existente
? state.itens.map((i) =>
i.id === produto.id ? { ...i, quantidade: i.quantidade + 1 } : i
)
: [...state.itens, { ...produto, quantidade: 1 }];
return {
itens,
totalItens: itens.reduce((s, i) => s + i.quantidade, 0),
totalPreco: itens.reduce((s, i) => s + i.preco * i.quantidade, 0),
};
}),
removerItem: (id) =>
set((state) => {
const itens = state.itens.filter((i) => i.id !== id);
return {
itens,
totalItens: itens.reduce((s, i) => s + i.quantidade, 0),
totalPreco: itens.reduce((s, i) => s + i.preco * i.quantidade, 0),
};
}),
atualizarQuantidade: (id, quantidade) =>
set((state) => ({
itens: state.itens.map((i) =>
i.id === id ? { ...i, quantidade } : i
),
})),
limpar: () => set({ itens: [], totalItens: 0, totalPreco: 0 }),
}),
{
name: 'carrinho-storage',
storage: createJSONStorage(() => AsyncStorage),
}
)
);Usando no componente:
function BotaoCarrinho() {
// Seleciona apenas o que precisa — sem re-render desnecessário
const totalItens = useCarrinhoStore((s) => s.totalItens);
const adicionarItem = useCarrinhoStore((s) => s.adicionarItem);
return (
<TouchableOpacity onPress={() => adicionarItem(produto)}>
<Text>Carrinho ({totalItens})</Text>
</TouchableOpacity>
);
}Seletores e Performance
// Crie seletores reutilizáveis
const selecionarItensPorCategoria = (categoria: string) =>
useCarrinhoStore((s) => s.itens.filter((i) => i.categoria === categoria));
// Shallow comparison para objetos — evita re-renders
import { useShallow } from 'zustand/react/shallow';
function ResumoCarrinho() {
const { totalItens, totalPreco } = useCarrinhoStore(
useShallow((s) => ({ totalItens: s.totalItens, totalPreco: s.totalPreco }))
);
}Redux Toolkit: Quando Vale a Pena
Use Redux para apps enterprise com:
- Time grande (padrão estabelecido)
- Estado muito complexo com muitas relações
- Necessidade de time-travel debugging
- Cache de API com RTK Query
// store/pedidosSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const buscarPedidos = createAsyncThunk('pedidos/buscar', async (usuarioId: number) => {
const resp = await api.get(`/usuarios/${usuarioId}/pedidos`);
return resp.data;
});
const pedidosSlice = createSlice({
name: 'pedidos',
initialState: { itens: [], carregando: false, erro: null as string | null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(buscarPedidos.pending, (state) => { state.carregando = true; })
.addCase(buscarPedidos.fulfilled, (state, action) => {
state.carregando = false;
state.itens = action.payload;
})
.addCase(buscarPedidos.rejected, (state, action) => {
state.carregando = false;
state.erro = action.error.message ?? 'Erro desconhecido';
});
},
});Guia de Decisão
| Cenário | Solução |
|---|---|
| Auth, tema, idioma | Context API |
| Carrinho, preferências, UI state | Zustand |
| App enterprise com time grande | Redux Toolkit |
| Estado derivado complexo | Jotai |
| Cache de dados de API | React Query + Zustand |
Conclusão
Para a maioria dos apps React Native em 2026, a combinação Context para auth/tema + Zustand para estado de negócio cobre tudo com simplicidade e performance. Redux só vale a pena quando a complexidade do projeto justifica a verbosidade.