Hello,
I have a tabs
LiveComponent like this:
tabs.ex:
defmodule SqlrWeb.LiveComponents.Tabs do
use SqlrWeb, :live_component
@impl true
def mount(socket) do
{:ok, socket |> assign(:active_tab, nil)}
end
@impl true
def handle_event("on_tab_select", params, socket) do
{:noreply, assign(socket, :active_tab, params["tab"])}
end
end
tabs.html.heex:
<div>
<ul class="flex flex-wrap text-sm text-center text-gray-500 border-b border-gray-200 w-fit dark1:border-gray-700 dark1:text-gray-400">
<li :for={tab <- @tab} class="mr-2">
<a
href="#"
class={[
"inline-block px-3 py-1 rounded-t-lg hover:text-gray-600 hover:bg-gray-50 dark1:hover:bg-gray-800 dark1:hover:text-gray-300",
@active_tab == tab.id &&
"font-medium bg-gray-100 active dark1:bg-gray-800 dark1:text-blue-500"
]}
phx-click={@on_tab_select && @on_tab_select}
phx-value-tab={@on_tab_select && tab.id}
phx-target={@myself}
>
<%= tab.label %>
</a>
</li>
</ul>
<div :for={tab <- @tab} :if={@active_tab == tab.id}>
<%= render_slot(tab) %>
</div>
</div>
I also have another LiveComponent, a very basic one, just for testing:
detail.ex:
defmodule SqlrWeb.LiveComponents.Detail do
use SqlrWeb, :live_component
@impl true
def mount(socket) do
{:ok, socket |> assign(:state, nil)}
end
end
detail.html.heex:
<div>
ID: <%= @id %> State: <%= inspect(@state) %>
</div>
If I use the Detail LiveComponent inside the Tabs LiveComponent in a LiveView like this, it works as expected:
<div>
<%!-- Other stuff here --%>
<.live_component module={Tabs} id={"detail-tabs"} on_tab_select="on_tab_select">
<%!-- Explicit tab slots --%>
<:tab id="explicit-tab-1" label="One">
<.live_component module={Detail} id="explicit-detail-1" state="Explicit Detail 1" />
</:tab>
<:tab id="explicit-tab-2" label="Two">
<.live_component module={Detail} id="explicit-detail-2" state="Explicit Detail 2" />
</:tab>
</.live_component>
</div>
But if I want to enumerate the tabs dynamically based on an array assign of the LiveView:
@impl true
def mount(_params, _session, socket) do
details = [
%{id: "detail-1", state: "Detail 1 state"},
%{id: "detail-2", state: "Detail 2 state"}
]
{:ok, assign(socket, :details, details)}
end
and in the LiveView’s .html.heex:
<div>
<%!-- Other stuff here --%>
<.live_component module={Tabs} id={"detail-tabs"} on_tab_select="on_tab_select">
<%!-- Tab slots by comprehension --%>
<:tab :for={detail <- @details} id={"tab-#{detail.id}"} label={"#{detail.id}"}>
<.live_component module={Detail} id={"#{detail.id}"} state={"#{detail.state}"} />
</:tab>
</.live_component>
</div>
then I get the error
** (ArgumentError) cannot convert component SqlrWeb.LiveComponents.Detail with id "detail-1" to HTML.
A component must always be returned directly as part of a LiveView template.
Am I doing something that I shouldn’t? I have found the same error message in several other forum posts, but none gave me an ideea how to solve my issue.
Thanks.