Send received slots to another Phoenix.Component

Hello everyone,

I’m trying to pass a received slot (list) to another component within a HEEx template. Here’s the closest working example I have:


  slot :foo do
    attr :bar, :string, required: true
  end

  def parent(assigns) do
    ~H"""
    <.child>
      <:foo :for={foo <- @foo} {foo}>
        <%= if foo.inner_block, do: render_slot(foo) %>
      </:foo>
    </.child>
    """
  end

  slot :foo do
    attr :bar, :string, required: true
  end

  def child(assigns) do
    ~H"""
    <span :for={foo <- @foo} attr-bar={foo.bar}>
      <%= if foo.inner_block, do: render_slot(foo), else: "default" %>
    </span>
    """
  end

  # usage
  <.child>
    <:foo bar="1">foo</:foo>  # <span attr-bar="1">foo</span>
    <:foo bar="2"/>           # <span attr-bar="2">default</span>
  </.child>
  <.parent>
    <:foo bar="1">foo</:foo>  # <span attr-bar="1">foo</span>
    <:foo bar="2"/>           # <span attr-bar="2"></span>
  </.parent>

The issue with this solution is that the parent component always generates an inner_block for the slot. This solution is not valid if you need slots without inner_block

An alternative solution:

  def parent(assigns) do
    ~H"""
    <.child>
      <:foo :for={foo <- @foo} :if={is_nil(foo.inner_block)} {foo}/>
      <:foo :for={foo <- @foo} :if={not is_nil(foo.inner_block)} {foo}>
        <%= if foo.inner_block, do: render_slot(foo) %>
      </:foo>
    </.child>
    """
  end

In this case, we can replicate the inner_block == nil. However, the slot order gets disrupted, which isn’t ideal as the order is often crucial.

Additionally, an older solution from this thread where the slot is passed as an attribute doesn’t seem to work with LiveView 0.21.1.

Is there a way to seamlessly send a slot (or a list of slots) to a child component that I might have missed? Wrapper components can be very helpful! Any guidance would be much appreciated.

Thanks in advance!

I’m curious what the downsides are to just using the default slot as opposed to a named slot. You can pass the the list using render_slot/2

With named slots you can have a list, for example you can define many different columns for a table, o different elements for a list.

Maybe pattern matching?

Could you do pattern matching: %{foo: %{inner_block: nil}} = assigns ?

  def child(assigns) do
    ~H"""
    <span :for={foo <- @foo} attr-bar={foo.bar}>
      <%= if foo.inner_block, do: render_slot(foo), else: "default" %>
    </span>
    """
  end

  def child(assigns) do
    ~H"""
    <span :for={foo <- @foo} attr-bar={foo.bar}>
      <%= if foo.inner_block, do: render_slot(foo), else: "default" %>
    </span>
    """
  end