Thinking in miso
Step 3: Actions and update
An action is something that happened: the user typed, the server answered, a timer fired. Name actions after events, not after setters, and let update decide what they mean.
The vocabulary
data Action
= Init -- mounted: go fetch
| GotBookmarks (Either MisoString [Bookmark])
| QueryChanged MisoString
| TagPicked (Maybe Tag)
| Select BookmarkId
| BookmarkAdded Bookmark -- mailed up by the form
deriving (Eq, Show)QueryChanged rather than SetQuery: the name leaves update free to also reset the selection, or later to debounce a request, without renaming anything.
update is a fold
update :: Action -> Effect Ctx () Model Action
update = \case
Init ->
getJSON "/api/bookmarks" [] (GotBookmarks . Right) (GotBookmarks . Left . ms)
GotBookmarks (Right bs) -> bookmarks .= Loaded bs
GotBookmarks (Left err) -> bookmarks .= Failed err
QueryChanged q -> do
query .= q
selected .= Nothing -- a new search deselects
TagPicked t -> tagFilter .= t
Select bid -> selected %= toggle bid
BookmarkAdded b -> bookmarks %= fmap (b :)Every branch is a small, total function on the model. There is no await, no promise chain, no setState callback ordering: getJSON schedules the request and the answer arrives later as GotBookmarks, which is handled like any other action.
IO lives at the edge
io/io_for asynchronous work — the default. The scheduler runs it off the update thread and catches exceptions.synconly for cheap, must-be-ordered reads (alocalStoragelookup, measuring an element).mount = Just Initto kick things off when the component appears;unmountto clean up.- Long-lived sources are subscriptions — a websocket,
rAFSub,routerSub— not effects.
Example: debouncing the search
Because subs can be started and stopped from update, a debounce is a tiny sub rather than a library:
QueryChanged q -> do
query .= q
stopSub "debounce"
startSub "debounce" $ \sink -> do
threadDelay 250000
sink (Search q)Testing
update is an RWS and view is a function, so both are testable without a browser: run update on a model, compare the resulting model with ==, and render view to HTML with toHtml to assert on markup.
Next: connect the pieces and ship.