Testing if @inner_block is empty

I have a breadcrumb component that can override the default separator as below:

def breadcrumb_separator(assigns) do
    ~H"""
    <li role="presentation" aria-hidden="true" class={["[&>svg]:size-3.5", @class]} {@rest}>
      <%= if length(@inner_block) > 0 do %>
        <%= render_slot(@inner_block) %>
      <% else %>
        <.iconify icon="heroicons:chevron-right-solid" />
      <% end %>
    </li>
    """
  end

It feels a bit hacky that I’m using length/1 to test if there’s any content passed to the slot. Is there an idiomatic way to test if a component has content in its slot? Testing it directly with <%= if @inner_block do %> always renders true, presumably because the slot exists?

Either @inner_block != [] or !Enum.empty?(@inner_block)

2 Likes

Here is what worked for me.

defp slot_empty?(slot) do
  case slot do
    [] ->
      true

    slots when is_list(slots) ->
      not Enum.any?(slots, fn slot ->
        case slot do
          %{inner_block: inner_block} when is_function(inner_block) ->
            try do
              {:ok,
                inner_block.(%{}, nil)
                |> Phoenix.HTML.html_escape()}
            rescue
              _ -> :error
            end
            |> case do
              {:ok, html} ->
                html
                |> Phoenix.HTML.safe_to_string()
                |> String.trim()
                |> Kernel.!=("")

              _ ->
                true
            end

          _ ->
            false
        end
      end)

    _ ->
      true
  end
end
  ~H"""
  <header :if={not slot_empty?(@inner_block)} class="grid gap-4 pb-8 text-left">
    <%= render_slot(@inner_block) %>
  </header>
  """