Update return pipeline
Sometimes in the our update function we need to do many different things. For example:
- Change the state of the model
- Change some value in the browser query
- Conditionally load more data
- Do some analytics tracking
We can do all these things at once:
case msg of
SeeReport report ->
let
nextModel =
{ model
| stage = ReportVisible report
, loading =
if needsToLoadMoreData model then
Loading
else
model.loading
}
cmd =
Cmd.batch
[
if needsToLoadMoreData model then
loadMoreDataCmd
else
Cmd.none
, setSomeValueInUrl
, TrackEvent.track {... }
]
in
(nextModel, cmd)
... ->
But in these cases our update branches can get very complex very quickly. Making them difficult to understand and ripe for bugs.
Pattern
A nice way to make many things in an update branch is to break them by concerns and create a "return" pipeline.
case msg of
SeeReport report ->
(model, Cmd.none)
|> andThen (setStageToReportVisible report)
|> andThen loadMoreDataIfNeeded
|> andThen addKeyInUrl
|> andThen trackSeeReportEvent
In this case andThen
is a function like:
andThen : (model -> (model, Cmd msg)) -> (model, Cmd msg) -> (model, Cmd msg)
andThen fn ( model, cmd ) =
let
( nextModel, nextCmd ) =
fn model
in
( nextModel, Cmd.batch [ cmd, nextCmd ] )
This function takes another function that given the model
returns a (model, Cmd msg)
just like update
.
andThen
takes care of batching commands together.
Every function is the pipeline will be responsible for only one thing, which is a lot easier to understand. E.g.
loadMoreDataIfNeeded : Model -> (Model, Cmd Msg)
loadMoreDataIfNeeded model =
if needsToLoadMoreData model then
({ model | loading = Loading }, loadMoreDataCmd)
else
(model, Cmd.none)