No, that's clear Alex. Thanks for taking the time to write that out. This
is a great pattern. It accomplishes what I mentioned above, yet in a
cleaner way.
When I see something like this, I'm surprised how versatile union types can
be.
On Thursday, July 7, 2016 at 5:04:24 PM UTC-4, Alex Lew wrote:
>
> Here's one way to achieve the all-http-errors-should-go-to-root
> functionality described by OP. I'm not sure I love it, but thought I'd
> throw it in.
>
> A general idea of what's going on: every component, rather than just
> returning a `Cmd Msg`, produces a `Cmd (Wrapped Msg)`, where a `Wrapped
> Msg` can either be a message-for-the-root-component or a
> message-for-the-component-itself. When the parent uses `Cmd.map` on these
> wrapped messages, it lets root messages through unchanged, but tags other
> messages as being for-the-child.
>
> Here's the code. First, create a separate module called, e.g.,
> `RootMessage`, that looks like this:
>
> module RootMessage exposing (..)
>
> type RootMessage =
> LoginFailed Http.Error
>
> type Wrapped msg = Internal msg | ForRoot RootMessage
>
> tag : (childMsg -> parentMsg) -> Wrapped childMsg -> Wrapped parentMsg
> tag tagger msg =
> case msg of
> Internal m -> Internal (tagger m)
> ForRoot r -> ForRoot r
>
> Then, in each *non-root* module, you do something like the following.
> Here, we have a Child module that includes a Grandchild component.
>
> module Child exposing (..)
>
> import RootMessage exposing (..)
>
> type Msg = GrandchildMsg Grandchild.Msg
> | APIResponse String
> | ButtonPressed
>
> update : Msg -> Model -> (Model, Cmd (Wrapped Msg))
> update msg model =
> case msg of
> GrandchildMsg subMsg ->
> let
> (newModel, cmd) = Grandchild.update subMsg model.grandchild
> in
> { model | grandchild = newModel } ! [ Cmd.map (tag GrandchildMsg)
> cmd ]
>
> ButtonPressed ->
> model ! [ Task.perform (ForRoot << LoginFailed) (Internal <<
> APIResponse) Http.get... ]
>
> The trick here is in `Cmd.map (tag GrandchildMsg) cmd`. The `tag`
> function, defined in the RootMessage module, takes in a normal tag, like
> GrandchildMsg, but returns a "tagger" that only applies the `GrandchildMsg`
> tag to non-root messages. It leaves the root messages unaltered.
>
> Finally, in the root, you can handle these generated root messages:
>
> module Root exposing (..)
>
> import RootMessage exposing (..)
> import Child exposing (..)
>
> type Msg = Something | AnotherThing | ChildMsg Child.Msg
>
> update : Wrapped Msg -> Model -> (Model, Cmd (Wrapped Msg))
> update msg model =
> case msg of
> ForRoot LoginFailed err -> ...
> Internal Something -> ...
> Internal AnotherThing -> ...
> Internal ChildMsg subMsg ->
> let
> (newModel, cmd) = Child.update subMsg model.child
> in
> { model | child = newModel } ! [ Cmd.map (tag ChildMsg) cmd ]
>
>
> Sorry for the terseness -- happy to elaborate if something is unclear!
>
> -Alex
>
>
> On Thursday, July 7, 2016 at 11:30:08 AM UTC-4, Erik Lott wrote:
>>
>> Mark:
>>
>> That's lead me to think about but not yet write a command alternative
>>> that could also handle tasks that didn't need to be routed back to the
>>> originator and that could be used to send messages to the top-level (or
>>> elsewhere).
>>
>>
>> I think you've nailed it. The suggested ELM architecture doesn't easily
>> allow for application specific communication from the lower nested
>> components to the upper components. There are times when in the scope of an
>> overall architecture, you'd like to return some type of application wide
>> recognized event/message back up the update stack, and have any interested
>> ancestor component react to it... This is essentially how Commands
>> function. Commands are a globally recognized type, that are returned up the
>> stack, and although they may carry a local message type with them, the
>> command itself is never returned or reused. The Command reaches the
>> application root, and is processed by the core language.
>>
>> Like you said: I think what we need (or at least what I need) is our own
>> Application specific type to return up the stack instead of Cmd. An "Event"
>> union type possibly? And CmdEvt tag could wrap the native elm Cmd, and be
>> unwrapped at the root.
>>
>> type Event
>> = CmdEvt Cmd
>> | MsgEvt
>> | Unauthorized
>> | LoggedIn User
>> | ConfirmPopup CancelMsg OkMsg
>>
>>
>>
>> On Tuesday, July 5, 2016 at 1:30:51 PM UTC-4, Mark Hamburg wrote:
>>>
>>> The first option feels repugnant from an encapsulation standpoint. I've
>>> built the second option and it works but it increases the amount of
>>> boilerplate in hierarchical systems because we now have three results to
>>> deal with in update functions. That's lead me to think about but not yet
>>> write a command alternative that could also handle tasks that didn't need
>>> to be routed back to the originator and that could be used to send messages
>>> to the top-level (or elsewhere). That said, once one gets into replacing
>>> Cmd, the API request model makes a lot of sense.
>>>
>>> Mark
>>>
>>> On Jul 5, 2016, at 5:46 AM, Erik Lott <[email protected]> wrote:
>>>
>>> My app has several layers of nested components. Various components
>>> throughout the tree will need to interact with our API via http requests.
>>> If any API request returns a 401 - Not Authorized error, or a Timeout
>>> Error, the error needs to bubble up to the root component where is can be
>>> handled appropriately.
>>>
>>> What is the most idiomatic way of dealing with this?
>>>
>>> *1. Parent should pattern match against important child messages*:
>>> Reference
>>> <https://groups.google.com/d/msg/elm-discuss/QPqrJd4C78Y/_TLLg81SAQAJ>
>>> This could work, but would be unreasonable in this case. The root
>>> component would need to match against every failing api http request made
>>> by every child, grandchild, great-grandchild, etc, component in the tree.
>>> If a single pattern is missed, the app would be in an error state, so this
>>> is prone to mistakes.
>>>
>>> *2. Nested Components return additional info from the "update" function*:
>>> Reference
>>> <http://stackoverflow.com/questions/37328203/elm-0-17-how-to-subscribe-to-sibling-nested-component-changes>
>>> Each component returns an additional value from its update function like
>>> this:
>>> update : Msg -> Model -> (Model, Cmd Msg, SomeInfo)
>>>
>>> The parent component could inspect the returned "SomeInfo" value from
>>> its direct children, and act on that information if necessary. In my case,
>>> any nested component that makes http requests to our API would be
>>> responsible for returning a APINotAuthorized and APITimeout value to its
>>> parent, and its parent would do the same, until the error has bubbled up to
>>> the root component.
>>>
>>>
>>> Option 2 is simple and robust, and can be used to pass messages of any
>>> type, for any situation... but I'm wondering if I'm missing an obvious 3rd
>>> solution?
>>>
>>> --
>>> You received this message because you are subscribed to the Google
>>> Groups "Elm Discuss" group.
>>> To unsubscribe from this group and stop receiving emails from it, send
>>> an email to [email protected].
>>> For more options, visit https://groups.google.com/d/optout.
>>>
>>>
--
You received this message because you are subscribed to the Google Groups "Elm
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.