The Container & Presentational Pattern: Separation of Concerns in React
Source: Dev.to
Introduction
If you’ve been writing React for a while, you’ve probably created a “God Component.” It fetches data, manages dozens of state variables, handles validation, does some unrelated calculation, and also renders the UI. The result is a tangled, hard‑to‑maintain component.
Enter the Container / Presentational pattern (also called Smart vs. Dumb components). It separates the concerns of what the UI looks like from how the data is obtained and managed.
The Pattern Explained
Think of a high‑end restaurant:
- Container (Chef / Manager) – works in the kitchen, deals with suppliers (APIs), manages inventory (state), and enforces business logic. It doesn’t care about plating.
- Presentational (Waiter / Plater) – receives the prepared dish, arranges it beautifully, and serves it to the customer. It knows nothing about how the dish was cooked.
+---------------------------+
| CONTAINER COMPONENT |
| (Logic / Data) |
+---------------------------+
| 1. Fetches data (API) |
| 2. Manages state |
| 3. Defines handlers |
+-----------+---------------+
|
(Passes data via props)
|
v
+---------------------------+
| PRESENTATIONAL COMPONENT |
| (UI / Rendering) |
+---------------------------+
| 1. Receives props |
| 2. Renders JSX / CSS |
| 3. Looks fabulous |
+---------------------------+
Classic Example: A List of Crypto Currencies
Presentational Component (The “Pretty” One)
// CryptoList.jsx
import React from 'react';
const CryptoList = ({ coins, isLoading, onRefresh }) => {
if (isLoading) {
return Loading your digital gold...;
}
return (
## 🚀 To The Moon!
{coins.map((coin) => (
**{coin.name}**: ${coin.price.toFixed(2)}
))}
Refresh Prices
);
};
export default CryptoList;
Pure UI: receives data via props and renders it. No knowledge of where the data comes from.
Container Component (The “Smart” One)
// CryptoListContainer.jsx
import React, { useState, useEffect } from 'react';
import CryptoList from './CryptoList';
const CryptoListContainer = () => {
const [coins, setCoins] = useState([]);
const [loading, setLoading] = useState(true);
const fetchCoinData = async () => {
setLoading(true);
// Simulating an API call
setTimeout(() => {
setCoins([
{ id: 1, name: 'Bitcoin', price: 45000 },
{ id: 2, name: 'Ethereum', price: 3200 },
{ id: 3, name: 'Doge', price: 0.12 },
]);
setLoading(false);
}, 1000);
};
useEffect(() => {
fetchCoinData();
}, []);
// No JSX here – just passing data down
return (
);
};
export default CryptoListContainer;
Logic‑only: handles data fetching, state, and side effects, then hands the results to the presentational component.
Benefits
Reusability
The same CryptoList UI can display data from any source (e.g., “Favorites” vs. “All Coins”) without touching the styling logic.
Separation of Concerns
- Styling bugs → edit the presentational file.
- Data‑loading issues → edit the container file.
No need to scroll through hundreds of lines of mixed logic.
Designer/Developer Harmony
Designers can work safely in the presentational files (HTML/CSS) without risking infinite render loops, while developers focus on data handling in the containers.
Hook Twist
Since React Hooks arrived, the strict class‑based “container” pattern is less common. You can now keep the component functional and still separate concerns by extracting custom hooks.
// useCoins.js (custom hook)
import { useState, useEffect } from 'react';
export const useCoins = () => {
const [coins, setCoins] = useState([]);
const [loading, setLoading] = useState(true);
const fetchCoinData = async () => {
setLoading(true);
// Simulated API call
setTimeout(() => {
setCoins([
{ id: 1, name: 'Bitcoin', price: 45000 },
{ id: 2, name: 'Ethereum', price: 3200 },
{ id: 3, name: 'Doge', price: 0.12 },
]);
setLoading(false);
}, 1000);
};
useEffect(() => {
fetchCoinData();
}, []);
return { coins, loading, fetchCoinData };
};
The container component can now be a thin wrapper that simply uses this hook and passes its results to the presentational component.
Pitfalls
Over‑Engineering
Don’t split a trivial component (e.g., a simple submit button) into separate container and presentational files. If the component is only a few lines, the extra abstraction adds noise.
Prop‑Drilling Hell
If you find yourself passing the same prop through many layers (e.g., user={user} down 10+ levels), it’s a sign to introduce Context or a state‑management solution instead of deep nesting of containers.
Conclusion
The Container / Presentational pattern encourages dumb UI and smart logic. By keeping rendering concerns separate from data concerns, you gain reusability, clearer responsibilities, and smoother collaboration between designers and developers. Use it where it adds value, avoid unnecessary splitting, and consider hooks or Context to mitigate prop‑drilling. 🚀