Separating presentation from logic keeps UIs testable and changeable. Understand the family of MV* patterns and what each one is really trying to protect.
Why: mixing business logic into UI code makes it untestable and impossible to change one without breaking the other — the MV* patterns all separate the Model (state + rules) from the View (what the user sees), with a third piece coordinating them. When: apply separation whenever a UI has non-trivial logic. Where: the goal is always the same — keep the model pure and testable, and make the view a thin, replaceable layer.
All MV* patterns split the same concerns; they differ in the "middle" piece:
MVC Model + View + Controller controller handles input, updates model
MVP Model + View + Presenter presenter holds ALL view logic; view is dumb
MVVM Model + View + ViewModel view BINDS to a view-model; changes flow
automatically (React, Vue, Angular, SwiftUI)
Shared goal: a pure, testable MODEL and a thin, swappable VIEW.Why: seeing the separation in code makes it real — the model knows nothing about the UI, and the coordinating layer translates between user actions and model changes. When: keep every rule and piece of state in the model so it can be unit-tested without a UI. Where: modern component frameworks are essentially MVVM — component state is the view-model, and rendering binds to it.
// MODEL — pure logic, no UI, fully unit-testable:
class Counter {
private count = 0
increment() { this.count++ }
get value() { return this.count }
}
// VIEW-MODEL / component state (MVVM, e.g. React):
// const [counter] = useState(() => new Counter())
// <button onClick={() => { counter.increment(); rerender() }}>
// {counter.value}
// </button>
// The view just BINDS to the model's state and forwards events.Why: the failure mode of every MV* pattern is business logic creeping into the view (validation, calculations, workflow) — which makes the view untestable and duplicates rules — so guard the boundary. When: if you cannot test a rule without rendering, it is in the wrong layer. Where: this discipline is what makes a front-end maintainable as it grows; the framework does not enforce it for you.
Keep it OUT of the view:
- validation rules -> the model
- calculations / derived data -> the model (or a selector)
- workflow / what-happens-next -> the model / controller / presenter
Keep it IN the view:
- rendering, layout, formatting for display
- forwarding user events to the model layer
Test: "can I test this rule without a UI?" If no, it's in the wrong place.