How do I render a child component in 0.17 (like CounterList)?

566 views
Skip to first unread message

Brian Bugh

unread,
May 14, 2016, 10:33:10 PM5/14/16
to Elm Discuss
The very useful CounterList and Counter examples were removed from the Elm Architecture Tutorial with 0.17, and now I'm stuck making a child component. 😕 (I hope it comes back!)  

I've tried a lot of different things in the parent component view function, and now I'm down to trying to pass in a literal model to the child component's view function to get it to render. This is the error I get, and I don't understand it. I mean I know why it's happening—because GoalButton.view results in an Html GoalButton.Msg—but I don't understand what I should do instead.

-- TYPE MISMATCH ------------------------------------------------------- sdg.elm

The type annotation for `view` does not match its definition.

49| view : Model -> Html Msg
           ^^^^^^^^^^^^^^^^^
The type annotation is saying:

    Main.Model -> Html Main.Msg

But I am inferring that the definition has this type:

    Main.Model -> Html GoalButton.Msg



Here's the simplest example I could do:

-- GoalButton.elm

type alias Model =
  { index : String
  , goalText : String
  , enabled : Bool
  }

update : Msg -> Model -> Model
update msg model =
  case msg of
    SwapActive ->
      { model | enabled = (not model.enabled) }

view : Model -> Html Msg
view model =
  div []
    [ img [ src (imageSrc model), onClick SwapActive ] []
    ]

-- GoalList.elm (the parent)

type alias Model =
  { goalButtons : List ( ID, GoalButton.Model )
  }

view : Model -> Html Msg
view model =
  div []
    <| List.map (\(id, child) -> GoalButton.view child) model.goalButtons


Can someone help me understand what I'm doing wrong here? How is this supposed to work in 0.17?

I saw a partial example in the 0.17 upgrade plan but I don't see how that solves my problem; I don't have anything to Html.App.map to like that example; the child component handles its own events. 

Thanks for any help!

Nandiin Bao

unread,
May 15, 2016, 12:09:11 AM5/15/16
to Elm Discuss
Html.App.map

在 2016年5月15日星期日 UTC+8上午10:33:10,Brian Bugh写道:

Peter Damoc

unread,
May 15, 2016, 3:15:40 AM5/15/16
to Elm Discuss
Hi Brian,

The Elm Architecture Tutorial has a "nesting" example:

https://github.com/evancz/elm-architecture-tutorial/tree/master/nesting


If you worked with the previous elm-architecture-tutorial (the one with 8 examples) I have a port of that tutorial that you can consult to see how some of those concepts translate to 0.17:

https://github.com/pdamoc/elm-architecture-tutorial/tree/master/examples/4




--
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 elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

Brian Bugh

unread,
May 15, 2016, 6:50:24 AM5/15/16
to Elm Discuss
Ahhh, strange... I looked at that page yesterday and didn't see those examples. Thanks for the link!

While that is helpful, that doesn't quite answer my question. The answer to my question may be now that "you're doing it wrong" but I'm interested in the implications of how I'm doing it because it seems like the right way. In the examples given, the CounterList acts as a factory so it manages all of the events and Msgs for the child Counter. In my example, the parent GoalList only acts as a container, while the events and Msgs are in the GoalButton itself.

Counter - actions, but no events
CounterList - all of the events

GoalButton - actions and events
GoalList - no events, only map data from GoalButton models to make available to parent of GoalList

Am I doing this "wrong"? It seems to make sense to me based on encapsulation concepts that GoalButton, the intelligent actor, would manage its own events for the dumber container GoalList.

Peter Damoc

unread,
May 15, 2016, 7:26:24 AM5/15/16
to Elm Discuss
Brian, 

you're not doing this "wrong" necessarily. 
If I were to guess, I would guess that you are using some sort of intuition that was primed for object oriented concepts in a setting that doesn't work quite like that. 

In the Elm Architecture you have a model, a way to update that model and a view. 
Now, when things get complex, you can split that into multiple components and have the model be a composition of smaller models while the update and view are compositions of smaller updates and views. 

Maybe this picture can clarify things a little bit (it's no longer 100% valid but the gist of it is). 
http://staltz.com/img/mvu-unidir-ui-arch.jpg

When you say GoalList, the model is a list of goals. How do you update this list of goals? Well, the first thing you have to do is figure out which of the goals needs to be updated. An individual Goal has no idea that there are other identical Goals around it and so, it does not encapsulates that kind of knowledge. The `GoalList.update` does not care how to update a specific Goal, it uses the `Goal.update` to do that. All that GoalList.update cares is that there are multiple Goals and that it will receive messages that need to modify the list of models.  

GoalList is not an object that handles the stuff that another object (Goal) should do. 
GoalList si a module that defines the 3 main compositions. 

It might be useful to think about `Goal` more in terms of a triad of "Extract Methods" than in terms of a singular, encapsulated object that you can throw in a list an be done with it. 

 

Brian Bugh

unread,
May 15, 2016, 7:51:32 AM5/15/16
to Elm Discuss
"you are using some sort of intuition that was primed for object oriented concepts in a setting that doesn't work quite like that. " This is probably the case. Years of OO is seeming more like a hinderance than a help in Elm! :) 

It sounds like you're suggestion that GoalButton shouldn't be "smart", e.g. that Click events should be managed by the GoalList, not the GoalButton. This seems to be counter-intuitive for making components reusable. What if I have parts of the site where I want to use a GoalButton individually and not in a list? I want them to handle their own clicking in that case...

What about a form element, like a special drop down box that has extra behavior (it pops up a warning when a specific option is selected)? It's used in two places on the site (the sign up wizard, and user profile page). I wouldn't want to have to redefine the same "parent" behavior in two totally different parent Form components...

Thanks for taking the time to explain this, it seems fundamental to understanding Elm. 

Peter Damoc

unread,
May 15, 2016, 9:03:48 AM5/15/16
to Elm Discuss
The GoalButton is created when all three parts are put together: the model (data), the update (how the data changes) and the view (how the data is displayed). 
It is as smart as it can be BUT, in order for it to work, you need to place each of the three parts in their proper place in the composition. 

Functional programming works best when you have a small set of data types and a huge array of operations that you can do on these data types. You can compose those operations easily and get quickly new operations. 
Object oriented programming works best when you have a small set of operations but a large variety of that types that you can combine and get new datatypes that conform to the same limited set of operations. 
I found a wonderful description of this from Martin Odersky of Scala fame: https://youtu.be/Dxzbwq45tUA?t=170


Elm does not have enough facilities for Object Oriented programming in order to be used as a drop in replacement. One needs to learn the Elm Architecture way and use that instead. 

There have been some heated discussion started by people with mainly OO background but, from what I could understand, Evan (Elm's creator) is not willing to just add some random approach to that set of functionality. 

The Elm Architecture however, works out pretty nice once you get to understand it.  
The `init`, `update`, `view` are kinda like the small set of operations from OOP and instead of composing the objects, you compose each of the three independently. 
This provides a lot of clarity when you do the composition.  

Of course, you pay a price... and that price is that if you want to try and experiment with switching implementations for a GUI component (something that OO is very good at) you might have to change the code in 5 places (Model definition, init, Msg definition, update and view) instead of one. :)  





Brian Bugh

unread,
May 15, 2016, 9:32:52 AM5/15/16
to Elm Discuss
Thanks for the background, that's helpful. I'm not attached to object-oriented, I've used a lot of other paradigms too. Elm is just really weird. :)

My question is still open, though. I don't understand how to adapt my example to the one that will work correctly, while still being reusable. The parent (GoalList) does not care at all about the actions of the nested component (GoalButton) and I'm not sure how to set it up so I don't have to have a "listener/dispatcher" in the actions of GoalList.

Peter Damoc

unread,
May 15, 2016, 10:20:22 AM5/15/16
to Elm Discuss
Brian,

Again... I think you might still be thinking in terms of objects. 
There is no listener/dispatcher there. No intermediary object.  
What the update of GoalList does is just update the model which happens to be a list of Goals. It does that by making use of the Goal.update. It doesn't need to know what the messages of the Goal are, the only thing that it cares about is information about which goal it's suppose to update (ID) and then it just replaces in the model the data of that counter with the new data that it gets from Goal.update. It transforms the old model into the new model. :) 

What I think you want is to declare that you have a list of Goals objects (not a list of Goal.Model) and through some magic have those Goal objects update themselves. 
I think you don't want to have to write the GoalList.update because you consider that the system has all the information it needs when you say you have a list of Goal Objects and you perceive the update to be redundant somehow. 
Am I correct in framing it like this? 




On Sun, May 15, 2016 at 4:32 PM, Brian Bugh <brai...@gmail.com> wrote:
Thanks for the background, that's helpful. I'm not attached to object-oriented, I've used a lot of other paradigms too. Elm is just really weird. :)

My question is still open, though. I don't understand how to adapt my example to the one that will work correctly, while still being reusable. The parent (GoalList) does not care at all about the actions of the nested component (GoalButton) and I'm not sure how to set it up so I don't have to have a "listener/dispatcher" in the actions of GoalList.


Brian Bugh

unread,
May 15, 2016, 4:46:21 PM5/15/16
to Elm Discuss
Am I correct in framing it like this? 

That sounds correct. I don't know the correct term for "listener/dispatcher" but there must be some kind of event triggering mechanism or the onClick would never happen. What does Elm call it? 

The view of GoalButton has an onClick handler that eventually triggers an update call, right? (whatever the terminology is)

Homan Chou

unread,
May 18, 2016, 1:40:55 AM5/18/16
to Elm Discuss
Hi Brian,
The Elm Architecture Tutorial has a "nesting" example:
https://github.com/evancz/elm-architecture-tutorial/tree/master/nesting

If you worked with the previous elm-architecture-tutorial (the one with 8 examples) I have a port of that tutorial that you can consult to see how some of those concepts translate to 0.17:
https://github.com/pdamoc/elm-architecture-tutorial/tree/master/examples/4

The new updated nesting tutorials don't include the modified "fancier" list of counters example where the delete button is in the child Counter component.  In the 0.16 example a "context" was passed in from the parent with some signal forwarding magic.  In a world where there are no signals in Elm now, how does this work?

Peter Damoc

unread,
May 18, 2016, 1:54:51 AM5/18/16
to Elm Discuss
It works like you can see in the second link you've quoted. :) 




--
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 elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Homan Chou

unread,
May 18, 2016, 3:55:14 PM5/18/16
to elm-d...@googlegroups.com
Ah... thanks.  I read that wrong and thought you were linking the old repo.

What does "Dispatch(Remove)" mean in the Counter exposing part?

--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/GZidxLFlLtQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Peter Damoc

unread,
May 18, 2016, 4:05:55 PM5/18/16
to Elm Discuss
It means that it exports both the type Dispatch and the tag Remove. It's explicit with what it exports. 
It should have been "Dispatch(..)" because in cases like this you might want to expose all the Dispatch Tags. 

Homan Chou

unread,
May 18, 2016, 4:10:42 PM5/18/16
to elm-d...@googlegroups.com
Interesting... it's like it's own module.  I feel like there are a lot of little things like this that aren't covered in any of the getting started guides.  

Mark Hamburg

unread,
May 18, 2016, 4:40:07 PM5/18/16
to elm-d...@googlegroups.com
I'm hoping that's not how it is expected to work because it means that a view layout decision — we need to put the remove button in with the counter display and buttons — now spreads to the messages and update function. I much prefer the solution that someone else posted where viewWithRemove takes a context that provides a mapping for the counter messages and a message to send on remove. That reflects the fact that it's the view change that forces the programmer to say "okay, I can build that view but you need to give me more context to do so successfully."

Mark

Mark Hamburg

unread,
May 18, 2016, 4:42:51 PM5/18/16
to elm-d...@googlegroups.com
Or more succinctly, the issue to be dealt with in the counter with remove button case is that the view needs to be able to "send" more messages not that the counter update function needs to handle more messages.

Mark

Adam Waselnuk

unread,
Jun 14, 2016, 9:48:17 AM6/14/16
to Elm Discuss
Mark would you be able to provide a link to that solution you are talking about here?

Peter Damoc

unread,
Jun 14, 2016, 11:47:08 AM6/14/16
to Elm Discuss
Here is a version of the CounterList with Context. It mimics how the old Address context used to work. 

https://github.com/pdamoc/elm-architecture-tutorial/tree/master/examples/4b

The main idea behind this approach is that the resulting view needs to talk in the language of the parent so, each Context will have a `toParent` mapper that takes the Child message and maps it to parent's AND a series of parentMgs. In this case we have context.remove
The parent then creates the appropriate context for each child. 


Mark Hamburg

unread,
Jun 14, 2016, 4:11:37 PM6/14/16
to elm-d...@googlegroups.com
Thanks, Peter. You got there ahead of me (thereby saving me from writing an example).
Reply all
Reply to author
Forward
0 new messages