Pros, cons and reasons for using Cmd.map


#1

I know the elm basics, but can somebody please explain to me whether it is disadvantageous to use Cmd.map to tag lower module messages with the message used in the main module? An example that uses this method is elm-package. I want to make an SPA that also has pages, but I want to know whether this is the preferred way to communicate between different messages and update functions in different models. If you know of better methods to communicate between such modules (like pages and main module), please tell me about them or point me to a resource. Is Cmd.map dedicated for this use case or is it useful in other cases as well?

Thanks in advance!


#2

I think this depends on how you want a design the pages, I will try first analyze the code, explain the consequences, gives you an alternative and then you can model under your convenience a solution.

In the Main.elm is use only on the initialization of a page, I will try first to explain what is doing this code (maybe is redundant and you already understand, sorry if is that the case):

Based on the code they only use the Cmd.map on the stepXxx functions that are used in the stepUrl. So I will explain the architecture with the first stepXxx function and the stepUrl:

stepSearch : Model -> ( Search.Model, Cmd Search.Msg ) -> ( Model, Cmd Msg )
stepSearch model (search, cmds) =
    ( { model | page = Search search }
    , Cmd.map SearchMsg cmds
    )

This function takes a record with a { key : Nav.Key, page : Page } , and map the ( Search.Model, Cmd Search.Msg ) into a ( Model, Cmd Msg ), So this function is amapofSearch.Modelinto aModeland aCmd SearchMsginto aCmd Msgand returns them in atuple`.

The above design is mainly to replace the actual Model and trigger extra Cmds based on the Url, to perform this extra Cmds he needs to use the Cmd.map.

Disadvantages: Well the main one is that the Search.init function is not aware of that mapping so you can not expect that a Cmd SearchMsg Search.Msg is created if you are on the scope of that function.

How to avoid this?
Well if you want to give to the init function more context you can add an attribute (Msg -> msg) as part of the Seach.init function.

The actual implementation is like:

init : Session.Data -> ( Model, Cmd Msg )
init session =
    case Session.getEntries session of
        Just entries ->
            ( Model session "" (Success entries)
            , Cmd.none
            )

        Nothing ->
            ( Model session "" Loading
            , Http.send GotPackages <|
                Http.get "/search.json" (Decode.list Entry.decoder)
            )

And the new one will be like:

init : (Msg -> msg) -> Session.Data -> ( Model, Cmd msg )
init parentMsg session =
    case Session.getEntries session of
        Just entries ->
          ( Model session "" (Success entries)
          , Cmd.none
          )

      Nothing ->
          ( Model session "" Loading
          , Http.send (parentMsg << GotPackages) <|
              Http.get "/search.json" (Decode.list Entry.decoder)
          )

Also we need to change the original stepSearch to:

stepSearch : Model -> ( Search.Model, Cmd Msg ) -> ( Model, Cmd Msg )
stepSearch model (search, cmds) =
    ( { model | page = Search search }, cmds )

And pass the SearchMsg to the Session.init on the stepUrl function:

-- Irrelevant code line 212
    oneOf
        [ route top
            ( stepSearch model (Search.init SearchMsg session)
            )
-- Irrelevant code line 217

#3

Thank you very much for your response. I think I understand it. Are you saying that stepSearch is only to replace the model and run the commands of the init function when the page is loaded, and from there the Search page manages its own commands?

Could you also please elaborate on the disadvantage? Its not clear to me.


#4

Thank you very much for your response. I think I understand it. Are you saying that stepSearch is only to replace the model and run the commands of the init function when the page is loaded, and from there the Search page manages its own commands?

Yep exactly that.

Could you also please elaborate on the disadvantage? Its not clear to me.

Sure!!, If you arrive to the Page.Search module the init function have this signature init : Session.Data -> ( Model, Cmd Msg ) , this says this function gets a Session.Data, do something with the session and return a model with some Cmds in a tuple. If the signature is init : (Msg -> msg) -> Session.Data -> ( Model, Cmd msg ) you know that, the first parameter is a function that will transform your local Msg into a foreign msg, or parent msg, or something else. But this function will return an external msg and the init will transform local Msgs into this foreign msgs.

To summarize, the second signature explains what will happen with the Msg later.

Note: I will fix some errors on the previous post.


#5

Also some recomendations are:

and its youtube explanation:


#6

I wanted to use Cmd.map to do a scenario when API call returns “needs authorization” and you want to show a log-in dialogue to the user, and then take them back where they are (to repeat the action), but it didn’t work as I expected. What I did was something like Cmd.map NeedsLogin Cmd.none, but it was never produced by the runtime.

I ended returning a tuple of update : Msg -> Model -> (Model, List Msg, Cmd Msg) in my page modules, which sometimes return (Model, [NeedLogin], Cmd.none) when the login is needed.

Code is available here https://gitlab.com/k-bx/meetup/blob/master/frontend/src/Meetup/Pages/Meetup.elm#L97


#7

Cmd.none == Cmd.batch [] so, Cmd.map NeedsLogin Cmd.none == Cmd.batch [] == Cmd.none :wink:


closed #8

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.