Wrote an article about a recent bug we encountered in our app related to the vdom, browser animation frames, and stateful event listeners in the view. Hoping people find this insightful, or at least an interesting story.
I’ve found myself also gravitating towards the pattern you label as a smell, ironically for exactly the reasons you label it a smell. My reasoning goes: if you’re going to submit, you should submit the data that the user actually sees, rather than the data that the model has. I’m now rethinking that to be: if some action is sensitive, you should verify that the model matches the data in the view before performing it.
I mentioned this in footnote #3, something my co-worker had brought up after he’d read the article. He’d essentially said “maybe it’s not always a smell, in the case where the user is acting off of what they currently see in the view”.
The way I think of it, is that you should not pass absolute values in Msgs. A Msg should always pass a delta value (or no value at all if it is just a trigger or state change on the model, as noted in your article).
In your calendar example, instead of:
type Msg
= SetOffset Int
You can do:
type Msg
= SetDelta Int
Where the delta is -1, 0, 1 for PreviousDay, CurrentDay or NextDay, or some other value for a bigger change. Of course these are the same thing, just named differently, but its how you treat the value that matters:
type Msg
= SetOffset Int
| ...
update model msg =
case msg of
SetOffset val -> { model | offset = val }
...
Versus this way, where the value is applied as a delta to the model:
type Msg
= SetDelta Int
| ...
update model msg =
case msg of
SetDelta val -> { model | offset = model.offset + val }
...