What's wrong with using Html.map?

Hello,

here in the doc Html - html 1.0.0
is said that " This should not come in handy too often. Definitely read this before deciding if this is what you want." (but the link is actually a 404 error).
Html.map seems like a great way to reuse components to make bigger components.

What I have been doing until now is having the view function of a smaller component take a function as a parameter: a function that transforms the Msg of the small component into whatever kind of message the author of the bigger component want to use in his application.

Then I thought it would be better to have a way to map a type of messages to another type of messages directly and I found that Html.map existed. That seems like a great idea.
Is there something wrong with it? Why the warning in the doc (leading to a 404) ?

1 Like

It used to point to Scaling The Elm Architecture · An Introduction to Elm, though I’ve seen someone on Slack suggest that Structure · An Introduction to Elm is what it should likely link to now.

I think part of the what the Html docs are trying to suggest, though not too well, is that you shouldn’t break up your code until you need to. I.e. don’t do premature optimization around pages/components.

3 Likes

There is a recent threat that elaborates further on the howtos of this principle:

It is trying to discourage the use of the nested Elm architecture pattern Nested TEA - Elm Patterns. Commonly Html.map is used in this pattern. This pattern is useful but is best to leave it for when it is really needed. It brings a good amount of complexity and boilerplate, so it better to find alternative solutions first.

If Html.map is useful for you without the whole Nested TEA pattern then there is not reason not to use it.

So this implies that you can build even a fairly sized application using only one Msg type. In practice, this is rarely the case, only if you are building some demo/toy/one screen app.

That is why the referred statement in Html docs may be misleading in pursuing too much simplicity.

1 Like

I have about 60k lines of code running in production, a decent sized application. I do not see any benefit of HTML.map there. I think there is only one of them in this application. But this is not pages, it’s an application. Maybe the mapping makes more sense if you build a website with pages.
I used to do more of the nested tea structure earlier, but found it much easier to just not do it.
Then there is no “parent/child”, “child/parent” or sibling communication. It’s just model update and messages. Simple stuff that works without your head hurting.

3 Likes

So this implies using a single Msg type through your app, you still have hierarchy inside of it? Seems that any reasonably-sized app should have some form of hierarchal structure.

Something like that:

type Msg = 
  | CommonAppRelated
  | PartRelated (DoSomethingWithPart | DoSomethingElseWithPart)
  | OtherPartRelated (DoSomethingWithOtherPart | DoSomethingElseWithOtherPart)
  

?

So this implies that you can build even a fairly sized application using only one Msg type. In practice, this is rarely the case, only if you are building some demo/toy/one screen app.

That is why the referred statement in Html docs may be misleading in pursuing too much simplicity.

Generally if you do need to have something like that, the better solution is to take a Msg -> msg function that wraps the inner message type and return Html msg. The main advantage of this over Html.map is it doesn’t lock you into “one layer” of the message type. You can then potentially take another argument that results in a msg for something else, and the caller can provide whatever they want.

E.g: say you have some kind of reusable form. You might want internal messages to update the state of the form (some kind of FormMsg -> msg), but then take some kind of FormData -> msg for the user submitting the form, as it will be context dependent what that does, and it doesn’t really make sense for it to be internal to the form.

4 Likes

If you have a (Msg -> msg) function, you might use it through Html.map (or Cmd.map in an update function):

someView : (Msg -> msg) -> Model -> Html msg
someView toMsg model =
    Html.div
         [ onClick DoStuff ]
         [ ... ]
         |> Html.map toMsg

I could have done that without Html.map, but just wanted to show it can be useful in this pattern too.

1 Like

Html.map doesn’t really offer much value in that scenario, I would just apply the function directly (i.e: DoStuff |> toMap |> onClick). What I was trying to say is that if you pass the wrapping function down to wherever the Html is made, rather than using Html.map after it’s made, you get more flexibility which is generally preferable.

1 Like

The real point I was trying to make is that it is not really Html.map (or Cmd.map) that is the problem, its trying to split up an Elm application into stateful components that is. You would use Html.map (or Cmd.map) to do that, so it can be a code smell, but its not a certainty.

I agree with rupert, adding a (SmallComponentMsg -> msg) argument to your views or using Html.map is architecturally the same thing. It allows you to split your developments into components. Html.map allows the same kind of flexibility you would get using (SmallComponentMsg -> msg) functions. You’re not forced to pass a BigAppMsg constructor to Html.map, you can pass any function that produces a message to it. Or you could handle particular cases in the update function of your main application (or bigger component).

So Html.map and (SmallComponentMsg -> msg) functions are basically the same thing. The nice thing that Html.map allows is that, since you’re not forced to add a (SmallComponentMsg -> msg) parameter to the view function of your component, it’s immediate to use that component as a top level application if you want (the view function has the good signature to be fed to Browser.document, Browser.application etc…).

So I guess, my question becomes: why is it frowned upon in the Elm community to divide your development into components?
I mean, as of now, Elm is used to develop user interfaces. User interfaces are naturally made of components. Date pickers, schedules, tabbed panes, menus, road maps, grids, pages, music staffs, pdf viewers, toasts, chatboxes, graphs, are just a few examples that come to mind. Having an Elm module that implements a component is great for reuse. Why would it be a bad thing?

Say I have an application that allows you to produce and edit documents that are sequences of paragraphs, images and music staffs.

Wouldn’t it be stupid to insist on putting all the messages into one update function ?

Wouldn’t it be more reasonable to make
- a module that implements a music staff, that allows you to place a note on a staff when you click on it, change the time signature etc…
- an image holder module that allows you to resize an image by a drag, or other kind of behaviors
- a paragraph module that allows you to change its font size, font colors etc…
- And have the main application delegate these behaviors to the smaller components ?

I could then publish, say, the music staff component on package.elm-lang.org for other people to use, or improve. This is better for the Elm community than a monolithic approach, where the messages dedicated to my music staffs would be some subtypes of a Message type of some big application I’m the only person to use.

2 Likes

So Html.map and (SmallComponentMsg → msg) functions are basically the same thing.

The whole point of my post was that they aren’t. With Html.map you are locked into only one “level” of message type. Taking functions lets you take different ones (e.g: one for wrapping internal messages, one for some external result), which is more easily flexible. You can of course emulate one with the other (your component can have a message type that has branches for internal/external and then have map calls disassemble that), but some ways of doing things push you into certain patterns and mindsets, picking the more natural ones can make things easier.

So I guess, my question becomes: why is it frowned upon in the Elm community to divide your development into components?

It isn’t, rather, it’s a bad idea to automatically split all of your code into components, as it adds complexity and forces you to write a lot of boilerplate for little gain. Making each page it’s own isolated component in a multi-page application, for example, is generally a bad idea. They are only going to be used in one place, and often end up needing to touch shared parts of the model.

If you have something that really makes sense as a component, something that needs to be done in many places across your application and is reasonably self-contained, then yes, make it into a component.

1 Like

Just to be clear, I am definitely not promoting “components”. I also used Html.map AND a (SmallComponentMsg -> msg) together in my example, it was not a choice of one OR the other:

-- Passed in a (SmallComponentMsg ->  msg) arg as toMsg.
someView : (Msg -> msg) -> Model -> Html msg
someView toMsg model =
    Html.div
         [ onClick DoStuff ]
         [ ... ]
         |> Html.map toMsg
         -- Applied it with Html.map

I think the Html.map vs (SmallComponentMsg -> msg) is really a detail in this discussion at this point.

What I’d like to understand is the reason for this hate for components. Have you read my example of application here: What's wrong with using Html.map? - #13 by AvailableUsername
Do you think you’re going to have an easy time writing such an application with just one big update function for everything?

1 Like

I don’t think there is an inherent hate for components.

It’s more a case of splitting your code when there is a valid reason for it, rather than starting out automatically trying to come up with a component based architecture.

I’ve learnt from my own mistakes how starting out with an OOP style approach can cause you to run into difficulties - Rupert is trying to prevent people from making those same mistakes, but maybe sometimes it’s better to learn from them than avoid them without understanding why :man_shrugging:.

‘Components’ are not a bad thing in my view though. For instance, I’ve got a dropdown/select component that I use quite a lot in a current project. The reason I chose to build it as it’s own component is because I want specific behaviour from it, such as keyboard navigation, and as I need this in many places, it makes sense to me for this module to manage it’s own state, so that I don’t have all that replication of code across all the consuming modules (and repeated for every ‘instance’ in every module), and I didn’t want to push that responsibility on to every parent module. I also have the guarantee of the same behaviour everywhere without having to remember to handle all the keyboard events in every parent - so less chance of bug creeping in somewhere.

So I think there is a place for ‘components’, but not for ‘components’ sake.

Edit:

That’s not what anyone is suggesting, it’s simply easier to start out with a single file and single update function and then break it up when and where it makes sense.

2 Likes

I think the difference is this: Highly reusable things is probably ok to build as component.
But In react you might split your app into header component, leftSidebar component++
I think the strong message in the community is to prevent you from doing exactly that. This is what most people would find natural to do if coming from react, but you will have a really bad time in elm doing this.

2 Likes

I want to ask a more precise question about architecturing without Html.map but I don’t have the time right now and this thread is going to get closed. Hopefully I can write about that precise case this week-end.

3 Likes

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