Live_component in an Enumerable workaround?

Hello,

I’m getting a little stuck on a LiveView issue and wonder if some folks had some ideas/feedback. Essentially, I have a live_component that renders as a realtimesearch input field. I need to have this realtime functionality on one-to-many nested input field.

Oversimplified, but imagine a list has embedded items and each item must have a single category from a few thousand categories.

<.form :let={f} id="list" for={@changeset} phx-submit="save" phx-change="validate">
    <%= inputs_for f, :items, fn item -> %>
        <.live_component
            id={item.id <> "_category"}
            module={LiveSearchInput}
            form={item}
            user_return_to={@user_return_to}
        />
        <.link phx-click="remove_category" phx-value-item_index={item.index} class="text-red-500">
    <% end %>
    <.button phx-click="add-item" id="add-item" type="button"><%= gettext("Add") %></.button>
</.form>       

When I tried to do this, I get the following error about using inside of an enumerable:


    ** (ArgumentError) cannot convert component MyApp.Components.LiveSearchInput with id ""list_items_0_category"" to HTML.

A component must always be returned directly as part of a LiveView template.

For example, this is not allowed:

    <%= content_category :div do %>
      <.live_component module={SomeComponent} id="myid" />
    <% end %>

That's because the component is inside `content_category`. However, this works:

    <div>
      <.live_component module={SomeComponent} id="myid" />
    </div>

Components are also allowed inside Elixir's special forms, such as
`if`, `for`, `case`, and friends.

    <%= for item <- items do %>
      <.live_component module={SomeComponent} id={item} />
    <% end %>

However, using other module functions such as `Enum`, will not work:

    <%= Enum.map(items, fn item -> %>
      <.live_component module={SomeComponent} id={item} />
    <% end %>

Are there any workarounds or general approaches I’m overlooking?

For context, I’m modeling my real-time behavior from this example from Chris McCord.

This situation is why inputs_for/2 and inputs_for/3 exist. Check out the example under inputs_for/3.

1 Like

I think you can get creative with components which lets LV/Heex work. Use :for={x <- fields} to iterate? You should be able to peek inputs_for and just rewrite it as a component (might already even exist in the master branch?)

  attr :form, :any
  attr :field, :atom
  attr :fake_data, :list
  slot(:inner_block)
  
  # You would call input_type input_value, input_name somewhere
  def map_fields(assigns) do
    ~H"""
    <div :for={d <- @fake_data}>
      <%= render_slot(@inner_block, d) %>
    </div>
    """
  end

  def render(assigns) do
    ~H"""
    <section>
      <.form :let={f} for={:lazy} as={:thing}>
        <!-- after thinking, you don't really need this extra component, you could
             just <div :for={i <- changeset.data.items}>  or whatever. -->
        <.map_fields :let={id} form={f} field={:item} fake_data={[1, 2, 3]}>
          <.live_component id={"#{id}_my_comp"} module={MyWeb.Component} />
        </.map_fields>
      </.form>
 </section>
     """
 end
1 Like