Smart vs Presentational — and When It's Over-Engineering For years, the standard advice was: split every feature into two components. One "smart" component that fetches data, holds state, and knows about the outside world. One "dumb" (presentational) component that just receives props and renders markup, with no idea where any of it came from. That advice is still half-right. But treated as a rule to follow everywhere, it turns into ceremony — extra files, extra prop lists, and a maze of pass-through props that don't make the code easier to change, only harder to read. The decision worth understanding isn't "should I have two components?" It's "where does behavior live, separately from rendering?" Once you see that distinction, you'll know exactly when the split earns its keep and when it's just decoration. Why the split exists in the first place Separating behavior from rendering buys you three real things: 1. Testability. A presentational component that only takes props is trivial to test — render it with fixed data, assert on the output. No mocking a network call, no faking a store. 2. Reuse. The same rendering logic can sit under different data sources — a list that renders identically whether the data comes from a REST call, a cache, or a hard-coded fixture in a test. 3. Clarity of change. When a bug report says "the button doesn't look right," you know it's a rendering problem. When it says "the wrong data shows up," you know it's a behavior problem. Separate concerns, separate blast radius. Those benefits are real. But notice none of them require two components . They require separating behavior (fetching, state, side effects) from rendering (what the DOM looks like for a given input). Two components is one way to achieve that. It is not the only way, and increasingly, not the best default. When the split becomes ceremony Watch for these signs that you're following the pattern out of habit rather than need: Prop drilling for its own sake. A "smart" parent passes eight props through a "presentational" middle component that doesn't use them, just to hand them to a grandchild. That's not separation of concerns — that's indirection with no payoff. A container with exactly one consumer. If the presentational component is only ever rendered by its one container, splitting them bought you two files and zero flexibility. Duplicated boilerplate. Every "smart" component reduced to isn't behavior separation — it's a formality that adds a hop with no new capability. The tell is always the same: you added a seam, but nothing can now vary independently on either side of it. A seam that can't be exploited isn't architecture. It's decoration. The modern answer: extract the behavior, not the component Both ecosystems have moved the "smart" layer out of components entirely — into a reusable unit that isn't tied to a specific piece of markup at all. That unit is what should carry the logic; the component stays presentational by default, and you split into a second component only when you have a genuine second consumer. In Angular Behavior lives in a service (or increasingly, a / -based store), and the component consumes it directly — no separate "container" component required: The service is the "smart" layer. is presentational in spirit — it renders what the service exposes — without needing a second wrapper component to enforce that boundary. In React The equivalent is a custom hook. It extracts exactly the same behavior a "container" component used to hold, but without forcing a wrapping component into existence: is the "smart" layer — testable on its own, reusable in any component that needs the same data. stays presentational without a second component existing purely to enforce that label. Either way, the rule is identical: the reusable, testable unit is the hook or the service — not a wrapping component. The framework changes the syntax, not the decision. The Trade-off: Is Extracting the Behavior Worth It? Extracting a hook or service instead of fetching inline is one more file and one more layer of indirection than just writing the fetch where you need it. Whether that's worth paying depends on what happens next. The pain, concretely The component that fetches its own data is fine — right up until requirements move past "fine": v1: calls inside a , sets state, renders. One screen, ships fast, works perfectly. v2: QA asks for a Storybook page showing the empty state and the loading state. To do that, you now have to either mock the network call in Storybook, or add a prop as an escape hatch — a prop that exists purely to defeat the component's own fetch logic. v3: Product wants the same list rendered from a live search's results instead of the full user list. The fetch is baked into the component, so the fastest path is copy-pasting into with the fetch swapped out — now two components with nearly identical rendering logic and two places to fix the next rendering bug. v4: A rendering bug shows up ("avatars overla…