A deep‑dive based on **“Learning Patterns” by Lydia Hallie & Addy Osmani”
“In React, one way to enforce separation of concerns is by using the Container/Presentational pattern.”
1 Why this pattern exists
Modern UIs juggle two distinct responsibilities: what data to show (application logic) and how to display it (view logic). The Container/Presentational pattern formalises that split:
Layer | Responsibility | Typical code |
---|---|---|
Presentational Component | Render the UI; accept data via props; stateless (or UI‑only state) | Pure function component |
Container Component | Fetch / transform data; hold state; pass props down | Often uses hooks, context, or data‑fetching libs |
Separating these layers keeps each file focussed and easier to reason about, test, and reuse. Presentational components remain visually driven, while containers orchestrate data flow.
2 Presentational component example
// DogsImages.js — Presentational
export default function DogsImages({ images }) {
return (
<section className="dogs-grid">
{images.map((src) => (
<img key={src} src={src} alt="Cute dog" />
))}
</section>
);
}
- Receives
images
via props. - No API calls, no business logic. Just transforms data → DOM.
- Can be reused anywhere images are needed. Presentational components are usually stateless and don’t mutate their props.
3 Container component example
// DogsImagesContainer.js — Container
import { useEffect, useState } from "react";
import DogsImages from "./DogsImages";
export default function DogsImagesContainer() {
const [images, setImages] = useState([]);
useEffect(() => {
fetch("https://dog.ceo/api/breeds/image/random/6")
.then((r) => r.json())
.then((data) => setImages(data.message));
}, []);
return <DogsImages images={images} />;
}
- Handles what data to show by fetching from an API, then delegates how to show it to
DogsImages
. - Renders nothing else; no styling of its own—typical of containers.
4 Hooks as a lighter alternative
With the arrival of React Hooks, you can move stateful logic into a custom hook and call it directly from the presentational component—skipping the wrapper layer:
// useDogImages.js
import { useEffect, useState } from "react";
export function useDogImages(count = 6) {
const [images, setImages] = useState([]);
useEffect(() => {
fetch(`https://dog.ceo/api/breeds/image/random/${count}`)
.then((r) => r.json())
.then((data) => setImages(data.message));
}, [count]);
return images;
}
// DogsImages.js
import { useDogImages } from "./useDogImages";
export default function DogsImages() {
const images = useDogImages();
return (
<section className="dogs-grid">
{images.map((src) => (
<img key={src} src={src} alt="Cute dog" />
))}
</section>
);
}
Hooks “save us the extra layer that was necessary” while keeping logic and view separate.
5 Pros & Cons according to the book
✅ Pros
- Clear separation of concerns—UI vs. data logic.
- Reusability—presentational components become flexible building blocks.
- Designer‑friendly—easy to tweak visuals without touching data flow.
- Simpler tests—pure functions are easy to snapshot or unit‑test.
⚠️ Cons
- In small apps the extra file may feel like overkill—hooks might be simpler.
6 When should you use it?
Use‑case | Recommended? |
---|---|
Complex screens with multiple data sources | Yes—keep UI files clean |
Shared UI components across pages | Yes—presentational FTW |
Tiny widgets with one API call | Maybe—consider a hook instead |
Non‑React projects | Concept still applies (e.g. adapters + views) |
7 Takeaways
Think “what” vs. “how.” The moment you notice UI tangled with fetching or state logic, reach for a Container/Presentational split—or a custom hook—to regain clarity.
Next in the series: Observer Pattern—stay tuned for #LearningPatterns21!
Content adapted from “Learning Patterns” (Patterns.dev, CC BY‑NC 4.0).
Top comments (0)