The container/presenter split separates data and side effects (fetching, mutations, navigation, analytics) from pure UI (layout, styles, conditional rendering from props). On mobile, route screens usually act as containers while reusable list rows, sheets, and empty states stay presenters - which makes Jest tests and Storybook-style previews far cheaper than mounting full navigation trees.
Container (smart / screen component): subscribes to remote data, global stores, route params, and platform APIs; maps them to a view model passed as props.
Presenter (dumb / pure component): renders JSX from props; avoids useQuery, useRouter, and implicit globals.
The boundary is a typed props interface - that contract is what you snapshot-test and what design tools consume.
Containers may call custom hooks (useProjectsQuery) to stay thin - hooks are not presenters.
Expo Router files (app/projects/index.tsx) should default-export a container that imports a colocated presenter from features/projects/.
Multiple containers can reuse one presenter when the UI is identical but data sources differ (search modal vs tab screen).
Presenter sneaks in useQuery - A "dumb" list that imports TanStack Query cannot render in isolation. Fix: Lift query to the container; pass projects and state down.
Leaky navigation in presenters - router.push inside a card component couples UI to Expo Router. Fix: Pass onPressProject; let the container decide the route.
Boolean prop soup - isLoading, isError, hasData, isRefreshing combos explode. Fix: Single discriminated state or status union prop.
Container still 800 lines of JSX - Splitting the file but leaving all markup in the container defeats the pattern. Fix: Move visual branches to the presenter; container returns one JSX element.
Over-splitting tiny screens - Two static labels do not need two files. Fix: Apply the split when fetch, navigation, or three+ UI states appear.
Unstable callback props - Inline onPress={() => router.push(...)} in the container recreates each render. Fix:useCallback in the container so presenter children can memo.
Testing the container by default - Mocking QueryClient, expo-router, and native modules in every test is slow. Fix: Test the presenter for UI; reserve one integration test per critical container path.