Container/Presentational Pattern
Splits UI into presentational components that render from props and container components that handle data and side effects. This separation improves reuse, testability, and clarity, and is commonly expressed with custom hooks in modern React.
The container/presentational pattern (also called smart/dumb components) splits a UI component into two roles. A presentational component receives data and callbacks through props and renders markup; it holds no knowledge of where data comes from. A container component sources that data — from an API, a store, or context — and passes it down. The split keeps rendering logic pure and pushes side effects to a thin, well-defined boundary.
How It Works
A presentational component is a function of its props: given the same props it produces the same output, with no fetching, subscriptions, or global state. A container wraps it, performs the data work, and supplies props. In React, a UserListContainer might call a data source and render <UserList users={users} onSelect={...} />, where UserList only maps the array to list items.
The pattern predates Hooks. Originally containers were class components managing lifecycle and state; presentational components were stateless functions. Today the same separation is often expressed with custom hooks (data logic) feeding plain components (rendering), which is why a class-to-hooks refactor frequently revisits these boundaries.
When to Use It
Reach for it when a UI element is reused across screens with different data sources, when designers and engineers want a stable component library independent of backend wiring, or when you want presentational components that are trivial to snapshot-test and showcase in tools like Storybook. It also helps onboarding: the rendering layer reads top to bottom with no hidden effects.
Trade-offs
The cost is more files and indirection. For a one-off component the extra container is ceremony. Over-applying the split produces deep prop-drilling chains and "glue" containers that add little. Modern hooks blur the line — a component can own a useUsers() hook and stay readable without a separate container — so treat the pattern as a guideline, not a mandate. The real goal is separation of concerns, not a fixed file count.
Related Patterns
Custom hooks often replace container classes for data logic. The provider pattern supplies shared data that containers consume. Higher-order components and render props are alternative ways to inject behavior. On the architecture side it mirrors model-view-presenter, where the presenter (container) feeds a passive view.
Example
// presentational: pure render
function UserList({ users, onSelect }) {
return (
<ul>{users.map(u => (
<li key={u.id} onClick={() => onSelect(u.id)}>{u.name}</li>
))}</ul>
);
}
// container: data + behavior
function UserListContainer() {
const { users } = useUsers();
const navigate = useNavigate();
return <UserList users={users} onSelect={id => navigate(`/users/${id}`)} />;
}
The UserList can be reused with mock data in tests and stories, while the container concentrates all environmental coupling in one place.