I’ll assume readers are familiar with TEA. We were shown by Evan, in elm-sortable-table, how TEA can be used to create a “component” (and by component I simply mean a smaller part of a larger app).
Richard Feldman’s canonical SPA example uses this same TEA style within Pages.
We are led to expect that we can compose an app of Pages and components, which themselves may contain sub-pages or components, etc.
If each component has it’s init, view, update functions, and an internal model with standard type signatures, then everything should be fine if we follow Evan’s advice here.
I’ll quote the advice here as it’s very important, and the emphasis is Evan’s.
The data displayed in the table is given as an argument to
view
. To put that another way, theTable.State
value only tracks the details specific to displaying a sorted table, not the actual data to appear in the table. This is the most important decision in this whole library. This choice means you can change your data without any risk of the table getting out of sync. You may be adding things, changing entries, or whatever else; the table will never “get stuck” and display out of date information.
But what if “the details specific to displaying” are a function of “the actual data to appear”? In other words, our model
is a function of what occurs in view
.
Might this be a problem? Can it be avoided?
We might have:
type alias Model = {
manySubWidgetStates : List SubWidget.Model
}
view : {Parent.Model : parentModel} -> Model -> Html Msg
view {parentModel} model =
parentModel |> expensiveGenerateSubWidgetData |> List.map viewWidget |> ...
Here view
calls an expensive function to determine which sub-widgets will be rendered. But their cmds will be processed by update
which we expect will be ready with manySubWidgetStates
. How?
This will only work if update
runs expensiveMapToSubWidgetData
and stores the result.
So, we need to guarantee that update
has “fresh” data and that it perform’s the majority of view
’s work. Is this a problem? Yes.
First, view
is special in that we call it exactly when we need it, with up-to-date data in our hands. We can’t help but call it with the appropriate data at the appropriate time.
update
is a little different … if the parent gets a Cmd
wrapped up for our component, it’ll certainly have to send it to our component’s update, or very conspicuously throw it away. But, if we write/modify an update
and just hope that it will be called every time our parent’s model changes in some way, it could be very easy for the parent to accidentally not call our update
. And since our update
is doing work on data that is later supplied to view
, we break the most important decision in our app and risk data being out of sync.
Have I missed something? Is this avoidable?