In the first article, we established that a good component has a clear context and a single responsibility. Now the question is: how do you organize them together? The answer is the Container/Presentational pattern.
It’s a simple pattern, but a remarkably effective one. There’s no single agreed-upon definition, but here’s the one I find most useful:
- Presentational Components/Dumb : Components that only care about how a specific data is shown to the user.
- Container Components/Smart : Components that mainly care about what data is shown by which component to the user.
For simplicity, we’ll also use the terms “smart” and “dumb” throughout this article.
The core idea is to separate logic from view to distinguish components that display data from components that fetch or transform it.
The presentational side: revisiting atomic design
Atomic design is a methodology for building web interfaces, introduced by Brad Frost in 2015. It breaks UI design into five levels: atoms combine into molecules, which compose organisms, then templates, and finally pages.
For our purposes, we’ll only keep the first two levels. The others rarely pull their weight — templates and pages tend to be one-off assemblies that don’t travel well. The higher you go in the hierarchy, the blurrier the boundaries get.
Donc nos composants dumbs sont de deux catégories possibles.
So our dumb components fall into two possible categories.
Atomic components can afford to be somewhat generic — seeing a term like “item” here isn’t jarring. These are the reusable building blocks with no business logic: buttons, inputs, icons — the elements of your design system. Their job is limited to rendering and handling simple interactions.
Molecular components , on the other hand, live in a more concrete context. They combine several atomic components with a small amount of logic to serve a specific need in a specific situation. An article-card is a good example.
Making this distinction early saves a lot of time. You immediately know which components are project-wide reusables and which ones belong to a specific context — which in turn helps preserve both the context and the responsibility of each component.
The container side: what pages are actually for
Next come the components responsible for logic and data consistency. That role belongs to pages. A page is in charge of fetching, transforming, and distributing data to the child components that handle presentation. It owns both the logic and the layout.
Some developers prefer to delegate layout to other structures and keep pages purely logical. That’s a valid approach, but it tends to add complexity without much payoff. In practice, keeping both concerns in a single page component is usually the cleaner call.
Concretely: all HTTP calls and store interactions happen at the page level. The page then transforms the data and passes it down to its children in exactly the format they expect. This structure makes a unidirectional data flow almost inevitable. You can trace data from the back-end to the view in a way that’s predictable and easy to follow data enters the application, gets transformed at the top, and flows down in pieces to purely visual components.
The goal isn’t to encourage excessive props drilling or events bubbling up through every layer. It’s simply to limit side effects. As a result, the code stays readable and unexpected state mutations are kept in check reducing the risk of components falling out of sync and keeping the architecture stable and legible over time.
Why it works: one-way data flow
This organization isn’t arbitrary it follows from a more fundamental principle: unidirectional data flow. Data enters at the top, at the page level, and flows down to child components. Never the other way around. A dumb component receives data, renders it, and emits events but it never goes looking for what it should display.
That predictability is what makes the codebase readable six months later. When a bug appears, you know exactly where to look: if something looks wrong, the problem is in the dumb component. If the data itself is wrong, the problem is in the page. Debugging becomes mechanical rather than instinctive.
A few practical notes
Before wrapping up, a few things worth keeping in mind about this pattern:
- Nesting a smart component inside another smart component is perfectly fine.
- It’s not absolute — a dumb component can sometimes call an API, particularly if it manages its own configuration.
- Some frameworks and state management libraries — Vue.js with Pinia, for example — make this pattern largely unnecessary.
- Avoid drilling props through too many levels of components.
A solid architecture is the foundation of any scalable, maintainable project especially for applications that live for years and pass through many hands. This pattern won’t transform your codebase overnight. But six months from now, when a new developer opens the project for the first time, they’ll know exactly where to look for logic and where to look for UI. That’s the whole point.
Next week, the last article in this series will step back and look at the history of this pattern because understanding where ideas come from changes how you use them.



Top comments (0)