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 msgexternalMsg 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.
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.
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.
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 myPortCmdAdd 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 myPortCmdsAdd 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 LoadingDataTransform the model, error, or (Cmd) msg of a ComponentResult.
Transform a ComponentResult's error value, if it is in an error state.
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 PageMsgTransform 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 PageMsgGiven 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)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 ())
)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.
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 ).
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" 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.
"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"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
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.