This library helps move data between components, where
A Component is a model
that can be initialized, and updated.
In addition to returning a model
from init
or update
, a component can
Cmd msg
externalMsg
for the parent/caller to use.Importantly, a component's init
/update
may instead return just an error
, and let the parent/caller decide how to deal with it.
This is most helpful in large apps where you have constructs like "sub-pages" and component-style modules (i.e. having model + init + update + view).
The purpose of this library is to standardize boilerplate within one's app, not necessarily to reduce it.
A ComponentResult is an encapsulation of the typicall results of updating a component.
It can represent model state, as well as dispatched Cmd msg
, external messages and errors.
withModel : model -> ComponentResult model msg externalMsg err
At minimum, a non-error-state ComponentResult must always have a model. This creates a ComponentResult with a model.
Use withCmd
, withExternalMsg
, etc, to augment.
justError : err -> ComponentResult model msg externalMsg err
An error-state ComponentResult may be created with an error parameter.
Note that withCmd
, withExternalMsg
etc have no effect on an error-state component result.
Add Cmds and external messages to a ComponentResult.
withCmd : Platform.Cmd.Cmd msg -> ComponentResult model msg externalMsg err -> ComponentResult model msg externalMsg err
Add a Cmd msg
to a ComponentResult
. This is a noop for error-state ComponentResult
.
Batches cmd with any existing ones.
withModel myModel
|> withCmd myHttpGet
|> withCmd myPortCmd
withCmds : List (Platform.Cmd.Cmd msg) -> ComponentResult model msg externalMsg err -> ComponentResult model msg externalMsg err
Add a list of Cmd msg
to a ComponentResult
. This is a noop for error-state ComponentResult
.
Batches cmd with any existing ones.
withModel myModel
|> withCmds myHttpGets
|> withCmds myPortCmds
withExternalMsg : externalMsg -> ComponentResult model msg Basics.Never err -> ComponentResult model msg externalMsg err
Add an external message (intended for the caller to interpret) to a ComponentResult
which
does not yet have an external message. This is a noop for error-state ComponentResult
.
withModel myModel
|> withCmd myHttpGet
|> withExternalMsg LoadingData
Transform the model
, error
, or (Cmd) msg
of a ComponentResult.
mapError : (err -> newErr) -> ComponentResult model msg externalMsg err -> ComponentResult model msg externalMsg newErr
Transform a ComponentResult
's error value, if it is in an error state.
mapModel : (model -> newModel) -> ComponentResult model msg externalMsg err -> ComponentResult newModel msg externalMsg err
Transform a ComponentResult
's model, if it exists (i.e. it is not a justError
).
Typical usage:
update : Msg -> Model -> ComponentResult Msg Model externMsg err
update msg model =
case msg of
PageMsg pageMsg ->
Page.update pageMsg model.pageModel
|> ComponentResult.mapModel (\newPageModel -> { model | pageModel = newPageModel })
|> ComponentResult.mapCmd PageMsg
mapMsg : (msg -> newMsg) -> ComponentResult model msg externalMsg err -> ComponentResult model newMsg externalMsg err
Transform a ComponentResult
's cmds, if it has any.
Typical usage:
update : Msg -> Model -> ComponentResult Msg Model externMsg err
update msg model =
case msg of
PageMsg pageMsg ->
Page.update pageMsg model.pageModel
|> ComponentResult.mapModel (\newPageModel -> { model | pageModel = newPageModel })
|> ComponentResult.mapCmd PageMsg
map2Model : (model1 -> model2 -> newModel) -> ComponentResult model1 msg externalMsg err -> ComponentResult model2 msg Basics.Never err -> ComponentResult newModel msg externalMsg err
Given a function to map 2 models into a new model, and 2 ComponentResults with such models,
map the ComponentResults into a new one, maintinaing error state, if any, and batching Cmd msg
if any.
init : ComponentResult Model Cmd externalMsg err
init =
map2Model (\modelA modelB -> { a = modelA, b = modelB , sort = Default, ...})
(SubComponentA.init |> ComponentResult.mapCmd ComponentACmd)
(SubComponentB.init |> ComponentResult.mapCmd ComponentBCmd)
applyExternalMsg : (externalMsg -> ComponentResult model msg never err -> ComponentResult model msg newExternalMessage err) -> ComponentResult model msg externalMsg err -> ComponentResult model msg newExternalMessage err
Apply the internal externalMsg (if any). The caller therefore has the opportuinity to remove the bound externalMsg type (and optionally replace it).
In general, the idea is that the caller is an update
function calling into another (sub-component's)
update
function. The caller will get back a ComponentResult
and needs to transform that
into the ComponentResult
it will return to it's caller.
An externalMsg
is used to inform the caller that it may need to augment it's processing.
e.g.
update : Msg -> Model -> ComponentResult Model Msg externalMsg err
update model msg =
case ( msg, pageModel ) of
( AccountPageMsg pageMsg, AccountPageModel pageModel ) ->
AccountPage.update pageMsg pageModel
|> ComponentResult.mapModel (\newPageModel -> { model | pageModel = AccountPageModel newPageModel })
|> ComponentResult.mapCmd AccountPageMsg
|> ComponentResult.applyExternalMsg
(\externalMsg result ->
case externalMsg of
AccountPage.LoggedOut ->
result
|> ComponentResult.withCmd (Ports.logout ())
)
discardExternalMsg : ComponentResult model msg externalMsg err -> ComponentResult model msg neverExternalMsg err
Discard the externalMsg of a ComponentResult
(if one is present).
sequence : List (model -> ComponentResult model msg Basics.Never err) -> ComponentResult model msg Basics.Never err -> ComponentResult model msg neverExternalMsg err
Sequence several ComponentResult
returning operations. E.g. suppose we need to
initialize a model and immediately update it as well:
DataStore.init credentials
|> sequence
[ \model -> DataStore.update (DataStore.DeleteUserPosts user) model
, \model -> DataStore.update (DataStore.DeleteUser user) model
]
NOTE: Elm docs say "there are no ordering guarantees" for batched Cmds and the same is true here.
update
calls are processed in sequence but the resulting batched commands are not.
resolve : ComponentResult model msg Basics.Never Basics.Never -> ( model, Platform.Cmd.Cmd msg )
Given a non-error ComponentResult
with no external message, transorfm it into the familiar
( model, Cmd msg )
type.
This is useful at the top-level update
function, because the Browser package
requires a return of ( model, Cmd msg ).
resolveError : (err -> ComponentResult model msg externalMsg Basics.Never) -> ComponentResult model msg externalMsg err -> ComponentResult model msg externalMsg never
Provided a function that can map an error to a non-error-state ComponentResult,
we can accept any ComponentResult
and guarantee a return of a non-error ComponentResult
.
escape : ComponentResult model msg externalMsg err -> Result err ( model, Platform.Cmd.Cmd msg, Maybe externalMsg )
"Escape" out of the ComponentResult
format, and into Core Elm types.
Doing this loses the benifits of the ComponentResult
type and related functions.
This shouldn't typically be required in production, but might be handy for debugging/testing/prototyping.
tapModel : (model -> any) -> ComponentResult model msg externalMsg err -> ComponentResult model msg externalMsg err
"Taps" into a ComponentResult when a model is available. The only (useful) thing one can do with the model is use Debug to log info.
ComponentResult.withModel { myModel | user = newUser }
|> ComponentResult.tapModel (\model -> Debug.log "user is " model.user)
|> ComponentResult.withExternalMsg SomeMsg
tapError : (err -> any) -> ComponentResult model msg externalMsg err -> ComponentResult model msg externalMsg err
"Taps" into a ComponentResult when an error is available. The only (useful) thing one can do with the error is use Debug to log info. The ComponentResult supplied will be returned as is
someComponentResult
|> ComponentResult.tapError (\err -> Debug.log "error occurred: " err)
|> ComponentResult.withExternalMsg SomeMsg