elmelm-architecture

How to call a parent msg from component in Elm?


I have a modal window that can display different components inside it. Each component has it's own updater and messages, but I want to share a close button between them.

Thus I can't call "CloseModal" directly from my children — Elm doesn't allow me to call someone else messages. What are my options?


I thought I could call "Modal.Update.update Modal.Messages.CloseModal", but inside my components I have only chunks of a state. So it's not a option.

Then I found a way to pass messages from parent to child, but it doesn't help me to pass messages other way around. Or to siblings.


Solution

  • In short, you can not pass messages directly from child to parent or a sibling.

    Elm Architecture implements uni-directional message passing, in other words, your parent component is always aware of messages for child components before child component will receive a message.

    I have made a simple example of parent-child communication, it is way too big to embed it into an answer so that I will note only the key points here.

    Child

    Child component defines a set of Messages:

    type Msg
        = Update Model
        | Focus
        | Blur
    

    In it's update function we ignore Messages, intended for the parent component.

    update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model =
        case msg of
            Update value ->
                ( value, Cmd.none )
    
            -- Ignore the rest of the messages.
            _ ->
                ( model, Cmd.none )
    

    Parent

    In parent's update function we can pattern match required messages and react to them.

    The rest of the messages will go through default branch.

    update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model =
        case msg of
            NameMsg childMsg ->
                case childMsg of
                    {- We have intercepted a message from child component.
                       This part of the update function might be moved
                       to a separate function for better readability.
                    -}
                    Input.Focus ->
                        update (HelperMsg Helper.Show) model
    
                    Input.Blur ->
                        update (HelperMsg Helper.Hide) model
    
                    -- The default message passing routine.
                    _ ->
                        let
                            ( nameModel, nameCmd ) =
                                Input.update childMsg model.name
                        in
                            ( { model | name = nameModel }
                            , Cmd.map NameMsg nameCmd
                            )
    

    The example above concludes the child-parent and sibling communication. You can run the update function recursively as much as you want with any messages to any components.

    Sending Messages from child's update function

    Cmd.Extra exposes a function for sending messages.

    update : Msg -> Model -> ( Model, Cmd Msg )
    update msg model ->
        (model, message SomeMessage)
    

    PS: Translator pattern example is on my To-do, leave a comment if you want me to update the answer with it.