A divisão container/presenter separa dados e efeitos colaterais (fetching, mutações, navegação, analytics) de UI pura (layout, estilos, renderização condicional a partir de props). Em mobile, as telas de rota geralmente atuam como containers, enquanto linhas de lista reutilizáveis, sheets e estados vazios permanecem como presenters - o que torna os testes Jest e as pré-visualizações no estilo Storybook muito mais baratos do que montar árvores de navegação completas.
Container (componente smart / de tela): assina dados remotos, stores globais, parâmetros de rota e APIs de plataforma; mapeia-os para um view model passado como props.
Presenter (componente dumb / puro): renderiza JSX a partir de props; evita useQuery, useRouter e globais implícitos.
A fronteira é uma interface de props tipada - esse contrato é o que você faz snapshot test e o que as ferramentas de design consomem.
Containers podem chamar hooks customizados (useProjectsQuery) para se manterem enxutos - hooks não são presenters.
Arquivos do Expo Router (app/projects/index.tsx) devem exportar por padrão um container que importe um presenter colocalizado de features/projects/.
Múltiplos containers podem reutilizar um presenter quando a UI é idêntica, mas as fontes de dados diferem (modal de busca vs. tela de aba).
Presenter insere useQuery - Uma lista "dumb" que importa TanStack Query não pode ser renderizada isoladamente. Correção: Eleve a query para o container; passe projects e state para baixo.
Navegação vazando em presenters - router.push dentro de um componente de card acopla a UI ao Expo Router. Correção: Passe onPressProject; deixe o container decidir a rota.
Sopa de props booleanas - Combinações isLoading, isError, hasData, isRefreshing explodem. Correção: União state ou status discriminada única.
Container ainda com 800 linhas de JSX - Dividir o arquivo, mas deixar toda a marcação no container, anula o padrão. Correção: Mova os ramos visuais para o presenter; o container retorna um único elemento JSX.
Divisão excessiva de telas pequenas - Dois rótulos estáticos não precisam de dois arquivos. Correção: Aplique a divisão quando fetch, navegação ou três ou mais estados de UI aparecerem.
Props de callback instáveis - onPress={() => router.push(...)} inline no container recria a cada renderização. Correção:useCallback no container para que os filhos do presenter possam memo.
Testando o container por padrão - Mockar QueryClient, expo-router e módulos nativos em cada teste é lento. Correção: Teste o presenter para UI; reserve um teste de integração por caminho crítico do container.