Container and Presentational Components in Modern React

Separate components that read data and state from components that only render, using Hooks and props instead of class containers.

7 min read

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.

App.jsxApp.jsx
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.

App.jsxApp.jsx
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.

Container and presentational boundary

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

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.
RunePowered by Rune AI

Frequently Asked Questions

Is this pattern still recommended today?

The class-based version is mostly obsolete, but the idea lives on through custom Hooks. A component that reads data with a Hook and renders a presentational component is the modern form of the same split.

Do I always need a separate container?

No. A single component that calls one Hook and returns JSX is often enough. Split into a container only when the data logic and the rendering genuinely change for different reasons.

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.