Elm Architecture: State vs Model

1,936 views
Skip to first unread message

Sean Clark Hess

unread,
Sep 8, 2015, 4:14:40 PM9/8/15
to elm-d...@googlegroups.com
So I'm creating an elm-html app of moderate size, following the architecture, and I am running into a recurring problem: where to put model state that needs to be used in more than one view?

My app has two panes. On the left, there's a view where you can select an item, and on the right it shows the details of the selected item. Then I have a parent view that contains both of them. 

    MainView.Model = { left : ListView.Model, right : DetailView.Model }
    ListView.Model = { items : List Item, selected : Maybe Item }
    DetailView.Model = { selected : Maybe Item, someOtherState : a }

Notice how selected appears in both models. The ListView needs to know about it to highlight it, and the DetailView needs to know about it to render the details. 

In the elm architecture, it is implied that each sub-view owns it's model. It is allowed to overwrite it when you call its update function. 

So let's say I have a Select Item action. If I handle it in ListView's update, DetailView will get out of sync. So we must handle it in MainView's update. But then I have to update both ListView's and DetailView's selected property. And they are supposed to own their own model! I shouldn't be looking in to their model and writing a sub-property. 

How would you handle this? 

One idea I had was to give views both a Model and State (like in React). The Model is reconstructed in every view pass from the parent's data, and State is something they own and can rewrite in their own update function. It seems good so far, but I can't be the first person to run in to this problem. 

What have you done in the past? 








Laszlo Pandy

unread,
Sep 8, 2015, 5:04:29 PM9/8/15
to elm-d...@googlegroups.com
I think there are two concepts here which are being mixed together:
1. state (which can be updated, and is not accessible by the parent)
2. properties (which are static, and are given by the parent)

Your React analogy is a good one because React has these two concept which it calls state and props.

At the top level of your app, the list of items is state, and the selected item is also state. However at the level of your listview, the list of items is simply a property passed from it's parent. You can tell because the listview is not allowed to set the list of items, or modify it[1] (because it has to be shared with the detailsview).

A good rule of thumb: your state should be in the lowest model which is still common to all the views which need to access it. From there is should be passed down as a property to the views. So if both listview and detailsview need the selected item, then selected item must go in the model common to both of them.

[1] If in the future you want the listview to implement reordering, this is still not considered "modifying the state". The listview should get the list of items as a property and send an Action back up to its parent, which performs the modification.

Does this make sense?

--
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.

Sean Hess

unread,
Sep 8, 2015, 6:20:24 PM9/8/15
to Elm Discuss
Yes totally makes sense but I'm still not sure how things look in my example. So you're saying that selected is state, and the "lowest common model" is MainView. So it should have .selected in its model? Do ListView and DetailView *also* have .selected in their model? If so, how do you let them edit their other model properties but not that one? If not, how do you pass it in?

Laszlo Pandy

unread,
Sep 9, 2015, 1:30:06 AM9/9/15
to elm-d...@googlegroups.com
.list and .selected should be in the MainView model.
ListView.view should take two parameters: model (nothing) and props (.list and .selected), which is stored in MainView.model.

Peter Damoc

unread,
Sep 9, 2015, 2:15:13 AM9/9/15
to Elm Discuss
Hi Sean,

On Tue, Sep 8, 2015 at 11:14 PM, Sean Clark Hess <sean...@gmail.com> wrote:

So let's say I have a Select Item action. If I handle it in ListView's update, DetailView will get out of sync. So we must handle it in MainView's update. But then I have to update both ListView's and DetailView's selected property. And they are supposed to own their own model! I shouldn't be looking in to their model and writing a sub-property. 

The way I'm viewing Elm Architecture, is that you compose the models, updates and the views until you get only one of each. 

Technically, you don't handle the Select in the ListView's update, you handle it in the Main view by delegating to the ListView's update. Because of this, you also can update the DetailView by delegating to its update in the MainView's update so things do not get out of sync.

The hint is in the `foldp`. If you have the List Component by itself, you would have a `foldp` on its state BUT if you compose the component, you don't have the `foldp` anymore. The `foldp` is ONLY in the Main Component. So, each component cannot be said to have its own state but to operate one a piece of the main state of the program. You cannot get out of sync when there is only one state. :) 

My mind is still stuck in the objects communicating paradigm and is still uncomfortable with this way of composing BUT I have faith. I'm patiently waiting for my FP enlightenment. :) 

 

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

Alex Schenkman

unread,
Sep 9, 2015, 10:23:18 AM9/9/15
to Elm Discuss
Sean,

This short post: Thinking in React might help you clarify the idea Laszlo is proposing.

Laszlo Pandy

unread,
Sep 9, 2015, 10:41:18 AM9/9/15
to elm-d...@googlegroups.com
Great link. Thanks, it does explain what I was trying to say.

Sean Clark Hess

unread,
Sep 9, 2015, 11:24:48 AM9/9/15
to elm-d...@googlegroups.com
Great that answers my question (You pass an extra parameter to the views, separating "props" and "state"). I've done a lot of react programming, and it's very helpful to see how the patterns apply here in elm-land. 

Thanks everyone!

Dénes Harmath

unread,
Sep 14, 2015, 8:50:25 PM9/14/15
to Elm Discuss
I just applied this pattern to an Elm combobox implementation: https://github.com/thSoft/Terminology/blob/master/src/Terminology/Combobox.elm It couldn't have worked without this separation, so I wanted to say a thank you! :)

Amitai Burstein

unread,
Sep 15, 2015, 7:01:22 AM9/15/15
to Elm Discuss
@seanhess do you happen to have this in a public example/ repo? - I'd love to learn form your code

Миржан Иркегулов

unread,
Sep 15, 2015, 11:49:17 AM9/15/15
to Elm Discuss
Laszlo,

What is not entirely clear is what it means by “send an action back up to its parent”. Suppose I have 2 Elm files: GUI.elm and Select.elm, and GUI.elm implements 2 components, which are defined in Select.elm. This is similar to a pair of counters from Elm Architecture Tutorial (https://github.com/evancz/elm-architecture-tutorial/#example-2-a-pair-of-counters).

Select.elm contains three HTML elements: a <select> tag (list of items), an <input> to add new items, and add <button>. In other words, instead of a pair of counters, as in Elm Architecture Tutorial, I have a pair of lists of items.

The first list (pane) is list of players. The second list is a list of possible moves in a game. What this mean doesn't matter, the point is, I can add new players, and in the second pane I can add new actions for a player.

The second list changes, when you select different players. Different players have different actions. So ever time I select a player from the left pane, the list of actions is changed in the right pane.

Now how do I do that? If Select.elm has a model:

{ options : List String, selected : Maybe Int, inputField : String }

than I know how to update this Select.elm-local model - by using its local update function. But how do I tell GUI.elm to update its model of all players and actions to update correctly, once I add a new player or action? Suppose GUI.elm's model is:

{ game : { players : List String, actions : Dict String (List String) }
, playerPane : Select.Model
, actionPane : Select.Model
} ---- where Select.Model is the previous model

Sean Clark Hess

unread,
Sep 15, 2015, 11:50:27 AM9/15/15
to elm-d...@googlegroups.com

Example.elm is the parent
WordSelect.elm is the child

Please note that while I am a React expert, I'm new to elm, so I wouldn't take it as being too canonical. Also, I haven't figured out a good way to handle submitting the form. It's supposed to both clear the search field (local state), and report the new term to the parent (props).
Inline image 1one: 

Миржан Иркегулов

unread,
Sep 15, 2015, 12:07:40 PM9/15/15
to Elm Discuss
Sean, thank you for your example, it is really helpful. I think I was getting at

update : Action -> State -> (State, Event)

in your file WordSelect.elm. You need to pass some data from the child to the parent. Yes, it's true that the parent model contains all children models in its record fields, but there might be some additional record fields that contain fuller data. In your case it's words, in my case it's full list of players and full dictionary of actions per each player. You proposed wrapping the child update into `(State, Event)`, then figure out what do with the event in the parent update.

I think sending props from parent to child may be considered canonical. Props can be seen as analogous to Context in Example 4 of Elm Architecture Tutorial. But how to elegantly send data from children to parents, or synchronize similar (but not identical) data between siblings — this I don't understand yet.

Before your `(State, Event)` I thought that it's impossible to not unwrap child's Action. But child Action should be opaque, so if a child defines it `type Action = Add String | Delete String | ...`, parent should be unable to see `Add` or `Delete`, write? Your `(State, Event)` is a workaround for this, right?

Sean Clark Hess

unread,
Sep 15, 2015, 12:42:07 PM9/15/15
to elm-d...@googlegroups.com
Yes it's a workaround, and I'm unhappy with my solution for that part. I had tried to implement the strategy in example #4, but it didn't work, because I need to both clear the local state and submit the new word back to the parent. I couldn't figure out a way to send 2 messages as the result of an event. 

I'd love for someone more experienced to chime in about this problem. How would you improve my example?

----

Synchronizing data between multiple children that is similar but not identical is an easier problem to solve. The key is to not store every form of the data in your parent model. Instead, store the canonical version (parent gets to decide), then assemble the data each child needs as props in your view function. 

So calling child views should pretty much always look like this:

parentView address model =
  div []
    [ Child.view (Signal.forwardTo address Child) { something = formatDataForChild model.someData } model.childState ]

The important part is that props are re-created on every view call, so you aren't duplicating data. 

Not sure if that is what you were talking about. Does that help?

Миржан Иркегулов

unread,
Sep 15, 2015, 12:55:40 PM9/15/15
to Elm Discuss
FWIW if you hate tuples, you can put the resulting Event inside the State, so instead of pattern-matching in parent like `(result, event) = ...`, you'd use `result.event`. That's not a solution, just a questionable aesthetic improvement. (Wrapping all case branches of the child update into a tuple is really ugly).

Миржан Иркегулов

unread,
Sep 15, 2015, 4:25:40 PM9/15/15
to Elm Discuss
(BTW, Sean, when you have free time and desire, try to implement simple grandparent-parent-child architecture where you must propagate Event from child to grandparent. I currently have to unwrap and rewrap events, if I want child to be invisible from grandparent:

          event' =
            case event of
              Select.None -> None
              Select.Added player -> Added player

this is stupid, but I don't know how to avoid it yet)

Sean Clark Hess

unread,
Sep 15, 2015, 4:44:51 PM9/15/15
to elm-d...@googlegroups.com
It's my impression that almost every react framework of sufficient size recommends having some kind of global or top-level actions. For example, read about redux: https://github.com/rackt/react-redux (which was inspired by elm!)

They recommend having 2 kinds of components: "smart" and "dumb". 

"Smart" components are aware of being in the framework and would interface with dumb components to map their actions to global ones. 

"Dumb" component would work like in the elm-architecture. You have to handle them explicitly. 

I've been looking through the redux todomvc example for ideas: https://github.com/rackt/redux/tree/master/examples/todomvc. It's totally clear how this might be adapted to elm yet. I feel like we don't have enough tools yet: it still feels too hard to pass down a callback as a prop, but I guess you can do that by passing different addresses down. 

Just thoughts for now. I'll let you know if I get closer to something. 

Yosuke Torii

unread,
Sep 18, 2015, 6:40:30 AM9/18/15
to elm-d...@googlegroups.com
Hi Sean, I'm happy to find this topic. I'm also solving the similar problem. 

I just wrote another example. This is a simple "select and show details" app. `Selector` component has the list of document names and `Detail` component shows the selected one's detail.

I agree some models (in this example, `docs` the names of documents) should be shared at the top, but I'm still unclear whether separating State and Props is the solution or not. In this example, I use simple Model which contains both State and Props. As you say, the parent component should update more than one component. But does it really matter? It looks like synchronizing two models manually, but I think constructing Props and passing it to `view` is not so different from that.

Am I missing some viewpoints? Still not satisfied with my code though.

Yosuke Torii

unread,
Sep 18, 2015, 7:11:01 AM9/18/15
to elm-d...@googlegroups.com
Ah, sorry. I should have explained more about my code.

Parent.elm handles various Actions as below.

Action from Parent => update Parent(self), Selector

Action from Selector => update Selector(self), Detail

Action from Detail => update Detail(self), Parent(through Effects)

Sean Clark Hess

unread,
Sep 18, 2015, 11:54:10 AM9/18/15
to elm-d...@googlegroups.com
Well the issue with the synchronization is here: https://github.com/jinjor/elm-experiments/blob/master/sharing-model/src/Parent.elm#L72

I think the idea behind the elm architecture was to encapsulate each component. You need to know the component exists, and forward it its model and actions in update, but you shouldn't have to know the structure of the model, or the different ways (actions) that it edits itself. You can do it anyway, of course, and it's not that bad on a small project, but it leaves me with a bad taste in my mouth, and I worry that it would not scale as well. Like, once you had a very large app it would be very confusing because there are no clear lines between components. 

This is obviously not a concern at the size of most elm apps, but I've definitely run into sizes where this would matter many times in javascript applications.

What do you think? I like how in your code you don't appear to need to know the structure of the child model, just the actions. That's not so bad I guess. 

Raoul Duke

unread,
Sep 18, 2015, 12:04:09 PM9/18/15
to elm-discuss
What are the absolute minimum things components need to know about each other? What is as simple as possible but no simpler? What if sometimes certain components need extra info, but not all of them all the time? What if there's a pure hateosy approach where they know nothing but dynamically load up ui?

Yosuke Torii

unread,
Sep 18, 2015, 10:07:19 PM9/18/15
to elm-d...@googlegroups.com
Thanks. Now I got what you are doing by "Event". We should keep Action opaque so need extra data structure, which also solves the doubled updating problem, right?

I also feel pain of breaking the encapsulation, so carefully choose the "public" parts of Action that allow us partial pattern matching. This separation looks like your Action and Event. I don't have much confidence because I know exposing part of ADT is an anti-pattern.

My second question is about Props. Is that really needed? Props (or Context) cannot be updated by component (in this example, reordering the docs). Also, `view` function cannot trigger effects. In React.js, State is *really* private. But in Elm, state *can* be updated by others. So I'm not sure whether we should think about which the given data should be.



2015-09-19 1:04 GMT+09:00 Raoul Duke <rao...@gmail.com>:
What are the absolute minimum things components need to know about each other? What is as simple as possible but no simpler? What if sometimes certain components need extra info, but not all of them all the time? What if there's a pure hateosy approach where they know nothing but dynamically load up ui?

--

Amitai Burstein

unread,
Sep 20, 2015, 8:35:28 PM9/20/15
to Elm Discuss
The approach I was trying (without success) was to pass in the context the entire effect.

So the parent would pass the childContext and map it back; while the child action would decide when to invoke that effect.

Does the approach sound right?

Dave Keen

unread,
Nov 29, 2015, 7:10:04 AM11/29/15
to Elm Discuss
I'm also having the same conceptual problem with passing actions from a child to a parent.  I'm very new to Elm, but is there some way that the parent (which has access to its own Actions, plus any exposed actions of its children) can constrcut a set of 'allowed' Actions and pass them into the child so that the child can then call those 'external' actions, as well as its own 'internal' ones?  I've tried to implement something like this and can't get it all to type-check, but I'm not sure if that because its not possible or its just me :)

Sampo Vuori

unread,
Nov 29, 2015, 3:12:12 PM11/29/15
to Elm Discuss
You're on a right track I think :)

Here's a pretty minimal example:

https://gist.github.com/savuori/a54ea0d27dc2007b1950

Maybe the more experienced users here can tell if there's something horrible with this approach.

Dave Keen

unread,
Dec 2, 2015, 5:55:40 AM12/2/15
to Elm Discuss
This feels like a great pattern to me.  So each view component gets:

context - some subset of the model, prepared specially for the components needs (equivalent to React's Props)
callbacks - functions prepared by the parent, allowing the child to call actions.  Effectively exposing a minimal API from the parent to the child
address - an address that the component can use to send messages to itself (via forwardTo as per the Elm Architecture)
model - the components own entry in the parent's model, and what it updates (equivalent to React's State)

What do people think about that?

Антон Телеш

unread,
Dec 18, 2015, 1:25:38 AM12/18/15
to Elm Discuss
For me this architecture pattern is a step back. This is exactly how pure react.js works without flux or redux — actions are being bubbled up through component tree.
For me redux architecture seems more predictable because it means all your actions are stored in one place, all your reducers are placed in one place. So you know where to look for implementation of business logic.
Also it means there is no necessity for bubbling events up the tree — your grandchild component can interact directly with redux by calling an action which then triggers all three update (unidirectional data flow).

So if other benefits of Elm (strong type safety, functional background) could be applied to redux architecture, I would me much more happy with it.
Reply all
Reply to author
Forward
0 new messages