Contextos divididos, provedores memoizados e padrões de seletor para evitar tempestades de re-renderização. O Contexto do React é integrado e funciona para valores que mudam infrequente - tema, localidade, sessão de autenticação. Um único mega-contexto atualizando a cada segundo pode prejudicar o desempenho da rolagem de listas em dispositivos Android de gama média.
// session/SessionProvider.tsx - estado vs ações divididos; valor memoizadoimport { createContext, useCallback, useContext, useMemo, useState, type ReactNode,} from "react";type User = { id: string; displayName: string };type SessionState = { user: User | null; status: "loading" | "authenticated" | "anonymous";};type SessionActions = { signIn: (user: User) => void; signOut: () => void;};const SessionStateContext = createContext<SessionState | null>(null);const SessionActionsContext = createContext<SessionActions | null>(null);export function SessionProvider({ children }: { children: ReactNode }) { const [state, setState] = useState<SessionState>({ user: null, status: "loading" }); const signIn = useCallback((user: User) => { setState({ user, status: "authenticated" }); }, []); const signOut = useCallback(() => { setState({ user: null, status: "anonymous" }); }, []); const actions = useMemo(() => ({ signIn, signOut }), [signIn, signOut]); return ( <SessionStateContext.Provider value={state}> <SessionActionsContext.Provider value={actions}>{children}</SessionActionsContext.Provider> </SessionStateContext.Provider> );}export function useSessionState() { const ctx = useContext(SessionStateContext); if (!ctx) throw new Error("useSessionState requires SessionProvider"); return ctx;}export function useSessionActions() { const ctx = useContext(SessionActionsContext); if (!ctx) throw new Error("useSessionActions requires SessionProvider"); return ctx;}// --- Consumidor: cabeçalho precisa apenas do displayName ---import { Text, View } from "react-native";export function HeaderGreeting() { const { user, status } = useSessionState(); if (status === "loading") return <Text>…</Text>; return <Text>Olá, {user?.displayName ?? "Convidado"}</Text>;}// --- Consumidor: configurações precisam apenas de signOut - NÃO re-renderiza quando o displayName do usuário muda// se você importar apenas useSessionActions e nunca useSessionStateimport { Pressable } from "react-native";export function SignOutButton() { const { signOut } = useSessionActions(); return ( <Pressable onPress={signOut}> <Text>Sair</Text> </Pressable> );}
// Padrão de hook seletor sem biblioteca extra - assina um campoimport { useRef, useSyncExternalStore } from "react";function useSessionSelector<T>(selector: (state: SessionState) => T): T { const state = useSessionState(); const selectorRef = useRef(selector); selectorRef.current = selector; return useSyncExternalStore( () => () => {}, () => selectorRef.current(state), () => selectorRef.current(state) );}// Uso: re-renderiza apenas quando o id do usuário muda (otimização manual - Zustand faz isso nativamente)export function UserIdLabel() { const userId = useSessionSelector((s) => s.user?.id ?? "anon"); return <Text>ID: {userId}</Text>;}
O que isso demonstra:
Contextos divididos - componentes que apenas chamam signOut evitam re-renderizações quando displayName muda (se nunca acessarem o contexto de estado).
useMemo / useCallback estabilizam o objeto de ações - evita quebra de memoização de filhos.
Padrão de seletor com useSyncExternalStore é uma ponte antes do Zustand - veja limitações em Deep Dive.
Sessão é identidade, não cache de API - detalhes do perfil ainda vêm do TanStack Query.
O React re-renderiza todos os consumidores de um contexto quando a identidade ou o conteúdo de Provider value={...} aciona uma atualização. Um erro comum:
// ❌ Novo objeto a cada renderização - todos os consumidores re-renderizam<AppContext.Provider value={{ user, cart, theme, setTheme }}>
Mesmo que apenas cart tenha mudado, os consumidores de theme re-renderizam porque o objeto de valor é novo.
Contexto único com { state, dispatch, extras } - Cada dispatch re-renderiza leitores de extras. Correção: Dividir estado e ações.
Funções inline no valor do provedor - signOut: () => setUser(null) quebra React.memo. Correção:useCallback.
Armazenar arrays buscados no Contexto - Atualizações de refetch re-renderizam a árvore. Correção: TanStack Query.
Provedor dentro de renderItem do FlatList - N provedores por linha. Correção: Um provedor em app/_layout.tsx.
Valor de contexto padrão com dados reais - createContext({ user: guest }) mascara provedor ausente. Correção:null + throw no hook.
Seletor useSyncExternalStore sem assinatura de loja - O exemplo acima ainda re-renderiza quando o contexto pai atualiza - a verdadeira granularidade fina requer uma loja externa (Zustand/Jotai).
Passar o cliente de API inteiro no Contexto - Estável, mas incentiva contexto gordo. Correção: Singleton de módulo ou DI - veja injeção de dependência.
O contexto em si é rápido - consumidores desnecessários são lentos. Divida contextos, memoize valores e evite colocar estado de alta frequência no Contexto.
Quantos provedores na raiz são muitos?
Três a cinco provedores bem escopados são normais (Query, Session, Theme, SafeArea). Mais de oito aninhados sugere consolidação.
Devo usar Contexto para o tema do React Navigation?
Expo Router / React Navigation aceitam uma prop theme no navegador raiz - muitas vezes mais simples do que Contexto personalizado apenas para cores.
Contexto vs Zustand para autenticação?
Ambos funcionam. Contexto + adaptador expo-secure-store é comum. Zustand ajuda quando muitos seletores leem sinalizadores de UI adjacentes à autenticação - veja Decisão ADR 7.
Como testar provedores?
Envolva com SessionProvider nos testes; simule o armazenamento seguro na fronteira. Prefira testar hooks com renderHook e valores de contexto simulados.
O React 19 melhora o Contexto?
O React 19 continua a renderização concorrente - consumidores de contexto ainda re-renderizam quando o valor muda. Dividir e usar seletores continuam sendo as melhores práticas no RN 0.86.
Posso combinar useReducer com Contexto?
Sim - useReducer no provedor, divida o dispatch em contexto de ações. O padrão corresponde ao Redux sem a dependência.
E sobre React.memo em itens de lista?
Memo ajuda apenas se as props forem estáveis. Corrija o provedor primeiro - memo em 200 linhas é um band-aid em uma tempestade.
A localidade deve viver no Contexto?
Sim - a direção i18n e as strings mudam infrequente. Emparelhe com expo-localization para o padrão do dispositivo; persista o override do usuário no AsyncStorage.
Como depurar tempestades de re-renderização?
React DevTools Profiler → gravar interação → encontrar renderizações em cascata de um provedor de contexto. Mude uma divisão de cada vez.
Passar dispatch via Contexto está OK?
dispatch é estável - seguro no contexto de ações. Consumidores que apenas despacham nunca precisam do contexto de estado.
Quando um contexto combinado é aceitável?
Protótipos e aplicativos com menos de 10 telas onde o profiler não mostra lentidão na lista. Refatore antes de enviar listas longas.