Using view helper with do block in LiveComponent causing "replace all" html updates

I am using tailwind, so I have made myself a few helper functions to stamp common css classes or html. One of those is a container that accepts a block, wrapping that content in a div.

  def container_box(opts \\ [], do: content) do
    %{class: class} = Enum.into(opts, %{class: ""})
    ~E"""
    <section class="classes... <%= class %>">
    <%= content %>
    </section>
    """
  end

One of my (stateful) components (a modal) calls this function at the top of it’s template.

<%= container_box() do %>

    <h1 class="font-bold mb-12">User</h1>

    <%= f = form_for @changeset, "#",
      id: "user-form",
      phx_target: @myself,
      phx_change: "validate",
      phx_submit: "save",
     ....

I noticed that when I interact with the form, my change digests as described in the console are just one fat lump. I just get an object that’s got one key, a huge string book ended by my “container_box” <section> tags.

If I remove the calls, my thin updates are restored and I get update-objects with more than one key.

My understanding is that live view will construct a mapping of all variables used and just send updates to elements where those variables would be effected. I guess because the whole template is wrapped in a call, it just sees it as one ever changing leex var/string/struct/object?

Now it all still works but it certainly feels like an indication that I am doing something wrong.

Is there an easy fix that still lets me DRY up the template?

Should my container be another stateless live component? I actually had it like that some time ago, but then read that it’s a wasteful manner when I just want to wrap some strings around something.

I know I can make a new css class that applies all the tailwind classes, but I’m curious if there is a LiveView trick since I have other helpers which do more structural work beyond pinning some classes so I think I might hit this issue again later.

Have a look at LiveEEx Pitfalls section of the documentation:

When it comes to do/end blocks, change tracking is supported only on blocks given to Elixir’s basic constructs, such as if, case, for, and friends. If the do/end block is given to a library function or user function, such as content_tag, change tracking won’t work.

Having recently started working with Tailwind, I too was looking for a solution to its madness (kidding, I’m a newly convert). If there’s something I would suggest for composing components similar to how you would do it in React/Vue that is Surface.

Ah, yes, like I thought. Sometimes you read the docs so often you forget whats in them.

Yeah tailwind is very nice for iteration, not 100% sure how I like it on the next step, at least with LiveView. If youre using something that lets you make (non-LV) components easily I think you can avoid some of the pain.

Surface would have the same problems right? I guess it depends where its compile step fits in the LiveView pipeline.