La división container/presenter separa datos y efectos secundarios (obtención de datos, mutaciones, navegación, analítica) de UI pura (layout, estilos, renderizado condicional desde props). En mobile, las pantallas de ruta suelen actuar como contenedores mientras que filas de lista reutilizables, sheets y estados vacíos permanecen como presentadores - lo que hace que las pruebas de Jest y las vistas previas al estilo de Storybook sean mucho más baratas que montar árboles de navegación completos.
Contenedor (componente inteligente / pantalla): se suscribe a datos remotos, stores globales, parámetros de ruta y APIs de plataforma; los mapea a un view model pasado como props.
Presentador (componente dumb / puro): renderiza JSX desde props; evita useQuery, useRouter y globales implícitos.
El límite es una interfaz de props tipada - ese contrato es lo que snapshot-testas y lo que consumen las herramientas de diseño.
Los contenedores pueden llamar hooks personalizados (useProjectsQuery) para mantenerse delgados - los hooks no son presentadores.
Los archivos de Expo Router (app/projects/index.tsx) deben exportar por defecto un contenedor que importe un presentador colocado desde features/projects/.
Múltiples contenedores pueden reutilizar un presentador cuando la UI es idéntica pero las fuentes de datos difieren (modal de búsqueda vs pantalla de pestaña).
El presentador cuela useQuery - Una lista "dumb" que importa TanStack Query no puede renderizarse aisladamente. Solución: Levanta la query al contenedor; pasa projects y state hacia abajo.
Navegación con fugas en presentadores - router.push dentro de un componente de tarjeta acopla la UI a Expo Router. Solución: Pasa onPressProject; deja que el contenedor decida la ruta.
Sopa de props booleanos - Combos isLoading, isError, hasData, isRefreshing explotan. Solución: Un único prop de unión state o status discriminado.
Contenedor aún con 800 líneas de JSX - Dividir el archivo pero dejar todo el markup en el contenedor anula el patrón. Solución: Mueve ramas visuales al presentador; el contenedor devuelve un elemento JSX.
Dividir demasiado pantallas pequeñas - Dos etiquetas estáticas no necesitan dos archivos. Solución: Aplica la división cuando aparezca fetch, navegación o tres+ estados de UI.
Props de callback inestables - onPress={() => router.push(...)} en línea en el contenedor se recrea en cada renderizado. Solución:useCallback en el contenedor para que los hijos del presentador puedan usar memo.
Probar el contenedor por defecto - Mockear QueryClient, expo-router y módulos nativos en cada prueba es lento. Solución: Prueba el presentador para la UI; reserva una prueba de integración por ruta crítica del contenedor.