How should I make view function for Model updating?

View function is just a function returns Html Msg. Then it shouldn’t know about how to update Model, so view function throw Msg as view events, like ClickedButton.
However, NoRedInk/elm-sortable-table (https://github.com/NoRedInk/elm-sortable-table), which is forked from evancz’s repository, is written on an another idea.
Just one Msg constructor is used for model updating, and it is received from parent component. A new model is calculated on view function. It means “view function knows about how to update Model”.
Which is the best practice of view functions?

1 Like

Hello @misoton665,
You identified correctly that updating state and showing it should be separated. This package follows this principle as well: The return value of the view function is still a Html Msg, the model is not changed in the view, it’s updated in the update as expected.

But you are also correct that this package handles it’s state a little different than many other packages.
Often packages expect to get new data when it changes and then copy it into their own model. For example a date picker needs the current datetime when you it arrives for example from a subscription. But this creates a potential problem: you can have one time in your main model and another one in the timepicker model.

This is why ‘elm-sortable-table’ distinguishes between ‘the data to show in the table’ and ‘the state of the table’. The package handles only the update of ‘the state of the table’. It does not copy ‘the data to show in the table’. You handle this data separately however it makes sense.

But the table view function still needs both states, so you have two possibilities:

  1. Put a function in the model that tells the view later how to get ‘the data to show in table’. But you should avoid putting any function in your model, because it cannot be serialized and therefore makes debugging very hard.

  2. Combine both states into a config and pass it to the table view directly in the your view. This is what the the package does.

I hope I explained it well. Feel free to ask if you have more questions.

2 Likes

Thank you @brasilikum.
I understood the principle which the table view isn’t interested in the data to show in the table. The table receive the data, do not ‘have’.
However, I am also wondering about the position of the logic to update the table model (or the table state?).
When Msg provide just one constructor which is like SetTableState Table.State, view function should have the logic to update the Table.State. For instance, toHeaderInfo function calculate the information to show a header. Attribute msg in the information will be set the header, and an event to update Table.State is handled by its onClick attribute. I think that the onClick attribute has the logic, so a view function which returns the header has the logic to update Table.State indirectly.
About the separation of view and the logic, which from my understanding, every such logic should be depending on a update function, and should not be depending to any view function. So, I have felt that the view function know how to update Table.State.

Therefore, I think, “view function shouldn’t know how to update Model” is not applied to NoRedInk/elm-sortable-table. So, if I will create a new component, I don’t know I should select which view function pattern.

From the README:

I took some minor liberties with update to make the API a bit simpler. It would be more legit if Table.Config took a Table.Msg -> msg argument and you needed to use Table.update : Table.Msg -> Table.State -> Table.State to deal with it. I decided not to go this route because Table.Msg and Table.State would both allocate the same amount of memory and one version the overall API a bit tougher. As we learn from how people use this, we may see that the explicit update function is actually a better way to go though!

In short: You are right, the “logic” to update the internal State is hidden inside of the message constructors, and the proper way to do it would be to expose a Table.Msg type with an appropriate update function.

However, in this specific case, there is actually only one possible message (“the sort order has changed”), and all the information about that message (which column, ascending vs descending) is exactly the same information that is stored in the Table’s State. This means the “logic” of the update function consists of just copying those 2 fields from Msg to State.

Because of this, Evan decided against adding the additional complexity and overhead and go with a simpler API instead.

2 Likes

Thank you!
I understood why he designed this API.

I will try to think carefully about how can make API simpler when creating new modules.

The Model, update and view are so interconnected that in practice they will almost always be in the same module.

Sometimes, the state is very simple and the state transition is also very simple. This makes it possible to update the state within the event handler and, instead of having a triplet, only have the view.

Here is the counter example adapted to this pattern.

The key ingredients here are the fact that the state si very simple (in this case just an Int) and the fact that the state transitions are also very simple. With a more convoluted state transition, one can get into troubles with this pattern.

2 Likes

I think just having Msg report UI events in the most naive and direct way possible (like you first notice with ClickedButton) is very good.

elm-sortable-table is not like that, and after working with it, Im of the opinion that its not a good way to go at all.

1 Like

@pdamoc @Chadtech
I’ve understood the elm-sortable-table pattern more deeply. Thank you!

I think the elm-sortable-table pattern has many good points but this pattern can’t use for widely. So I want to understand when the elm-sortable-table pattern should be used.

In my understanding, the elm-sortable-table pattern can be used to make a simple API of modules which have a small state. I have to define where is a border of a small state module, to use this pattern. But I think it’s too difficult to answer because it is caused by personal sensation. So I must think whether I should use this pattern or not. This is an overhead of using this pattern.

So don’t use it if it makes your code harder to reason about.

I like the pattern and I use it sometimes, it is a good pattern in my perspective BUT, every good idea can be abused and turned into a bad idea. If you find yourself struggling to fit the solution to this pattern, maybe the pattern is not a good fit for your problem.

If you want to better understand it, you need to think about alternative implementations.

It all boils down to how complex is the state and how complex are the state transitions. As a rule of thumb, if you can fit the state in a simple tuple (3 pieces of simple data), I would consider that state simple enough.

If you have a lot of simple state transitions that are not really relevant to the context in which the view would be used, then it makes sense to abstract them away in a component.

The alternative would be to either use a full triplet OR to create a view with a monstrous Config.

I created this Ellie to try and show how updating the model from the view can sometimes lead to stale state being put into the model:

https://ellie-app.com/yVC9wPk33xa1

It is generally not a good idea - the view should just report UI events, and the update should figure out how to update state from those.

But the problem only occurs when events are coming in rapidly. I think for sorting a table based on a user clicking a heading to choose which column to sort by, its going to be ok. Just be aware of the issue, so you can spot it and understand how to deal with it when it happens.

3 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.