Search code examples
javascriptsignalsrxjsfrpelm

Elm Effects Mapped to Nested Component


In this example (RandomGifPair), how is the update corresponding to NewGif actually wired to execute after the parent component fires RandomGif.update act model.left? It seems like the RandomGif.update NewGif maybeUrl needs to be manually fired somewhere. To be a little more explicit, RandomGifPair fires its Left update action and gets back a model / effect pair by manually calling RandomGif's update function. The returned effect is executed through the Effects.map Left fx, and that goes on to the getRandomGif function in RandomGif

getRandomGif : String -> Effects Action
getRandomGif topic =
  Http.get decodeUrl (randomUrl topic)
    |> Task.toMaybe
    |> Task.map NewGif
    |> Effects.task

which, as far as I understand it, will go on to fire the NewGif action which, due to the Effects.map, is now also tagged with Left. The only part of the picture I'm missing is how this action is kept within the scope of RandomGif and the action corresponding to the NewGif case of this update actually fired:

update : Action -> Model -> (Model, Effects Action)
update action model =
  case action of
    RequestMore ->
      (model, getRandomGif model.topic)

    NewGif maybeUrl ->
      ( Model model.topic (Maybe.withDefault model.gifUrl maybeUrl)
      , Effects.none
      )

when Main.elm only has the update function from RandomGifPair and thus has no case for NewGif.

I'm sure the answer lies in specific details of ports, Effects.map, forwardTo or Tasks that I'm missing.

For reference, here is an attempt to solve the problem in javascript that contains an entry in the upper update function for NewGif and manually calls RandomGif.update inside of it. Probably not the best way to try to understand elm...


Solution

  • All actions come in to your top-level update function. There's no way to scope actions to a sub-update function - they always come in at the top. So, most programs will manually route actions down to the lower update functions. That's what's happening here. All of the actions must go into RandomGifPair.update, and it's up to that function to a) call sub functions and b) store the results in the right spot in the state. It can be surprisingly cumbersome.

    Here's the specific point in RandomGifPair.update that does the routing.

    Line 42 says "oh this is a Left action? Give me the act that's inside of there. NOW you have your desired NewGif or RequestMore stored in act, and you know that it was bound for the left one. Line 44 calls the lower update function, which knows how to handle that. Line 46 stores the result of the lower update function in the model (by recreating the whole model with the new left and re-using the old right).

    This is all obscured by the boilerplate around Effects. If you can understand how the actions flow first, then go back and apply the same logic to the Effects, I think that'll become clearer.