Container and presentational components split a React component into two parts: one that reads data and state, and one that only renders props. The class-era version of the pattern is mostly obsolete, but the separation it created survives through custom Hooks.
The original split
A presentational component is pure in the React sense. It receives props and returns JSX, with no fetch calls, no subscriptions, and no direct data access. A container component does the heavy lifting: it reads data or state and passes the result down as props.
The pattern became popular in 2015 when React components were written as classes. Containers used lifecycle methods to fetch and manage data, while presentational components stayed simple enough to reuse across screens.
Why class containers faded
Hooks replaced the class lifecycle methods that containers depended on. A custom Hook can subscribe to data, hold state, and expose it with a clean API, which is exactly the job the class container used to do. The migration was mostly mechanical: data logic moved from lifecycle methods into Hooks, while the presentational components stayed untouched.
Today the container is usually just a function component that calls a Hook. The data logic lives in the Hook, the state stays in the container when the screen needs it, and the presentational component stays unchanged.
The modern version
Start with a presentational component that renders an array and nothing else.
function ProductList({ products }) {
if (products.length === 0) {
return <p>No products match.</p>;
}
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}This component has no idea where products come from. It renders the same output for a static list, a filtered list, or server data.
A thin container supplies the data. It calls a custom Hook, handles the loading state, and passes the result down.
import { useProducts } from "./useProducts";
function ProductListContainer() {
const { products, isLoading } = useProducts();
if (isLoading) {
return <p>Loading products...</p>;
}
return <ProductList products={products} />;
}The container owns the data boundary and the loading decision. When the screen also needs local state, the container adds it with useState and passes it down, which keeps the presentational component free of both data and state logic.
Data flows down from the Hook through the container into the presentational component. Events are the only path back up, and the container decides whether they update local state or send a request.
Benefits of the split
- The presentational component is easy to test because it has no external dependencies.
- The same presentational component can render data from different containers.
- The data logic can be replaced without touching the markup.
The split also documents intent. A reader can tell at a glance which files deal with data and which only paint markup, so future changes stay localized and reviews go faster.
For the Hook side of this, see How to Create a Custom Hook in React.
Tradeoffs and when to skip it
The split adds a layer, and a layer is only worth it when it removes real coupling. If a component calls one Hook and returns a short JSX block, a separate container plus presentational pair adds indirection with no benefit.
The classic failure mode is a container that exists only to pass props straight through. That is prop drilling in disguise, not a useful boundary. Component Composition vs Inheritance in React shows how composition can avoid the same problem.
Use the split when:
- The same presentational UI needs to render from several data sources.
- You want to test the rendering without mocking a data layer.
- The data logic and the layout are large enough to change independently.
Otherwise, keep one component and one Hook. The pattern is a tool for a specific coupling problem, not a rule every component must follow. When rendering performance becomes the concern, tracing why a component re-renders is a separate problem from this structure.
Rune AI
Key Insights
- A presentational component renders props and owns no data access.
- A container reads data or state and passes values down as props.
- Hooks replaced the class lifecycle methods that containers used to need.
- The split makes rendering easy to test and reuse.
- Skip the extra layer when one component plus a Hook is already clear.
Frequently Asked Questions
Is this pattern still recommended today?
Do I always need a separate container?
Conclusion
A container reads data and state, while a presentational component only renders props. Modern React reaches the same goal with a custom Hook called from the container, which keeps rendering pure and data logic replaceable.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.