sbennett33

sbennett33

Feature Request: LiveView Render Delegation

When building a component library, it is often useful to give users the ability to customize the underlying element or component to use.

For example, a dialog component may use a by default to trigger its modal, but could allow users to substitute the default for a

or a custom function component instead.

Other frontend frameworks enable this by allowing the user to pass an asChild attribute that tells the component to delegate rendering of the component to the component passed in the inner block.

In React frameworks like shadcn/ui for example you can declare a trigger for a dropdown menu like this:

  # React
  <DropdownMenuTrigger>Open</DropdownMenuTrigger>

Which would render a regular button by default:

  # Default button trigger
  <button data-item=“trigger”>Open</button>

Or you can pass an asChild attribute which would delegate rendering to whatever child component you specify like so:

  # React
  <DropdownMenuTrigger asChild>
      <Button>Open With Icon <Icon name=“plus” /></Button>
  </DropdownMenuTrigger>

Which would render:

  # Custom button with icon
  <button data-item=“trigger”>Open With Icon <span>…svg icon...</span><</button>

One important thing to note is that the parent DropdownMenuTrigger component is able to pass props down to the rendered child component so both the default button and the custom button component get the data-item=“trigger” attribute applied.

Because I can’t pass attributes to the render_slot/2 function I haven’t been able to figure out a way to accomplish something similar using LiveView.

What I’d really like to have is something like a dynamic_slot/1 component that I could use like this:

  attr :as_child, :boolean, default: false
  attr :class, :string, default: ""
  attr :rest, :global

  slot :inner_block

  def dropdown_menu_trigger(assigns) do
    if assigns.as_child do
      ~H"""
      <.dynamic_slot slot={@inner_block} data-part="trigger" class={@class} {@rest} />
      """
    else
      ~H"""
      <.button data-part="trigger" class={@class} {@rest}>
        {render_slot(@inner_block)}
      </.button>
      """
    end
  end

Where the dynamic_slot/1 component would merge its assigns with those of the @inner_block passed in the slot attribute and then render the @inner_block.

I don’t know if something like this is already possible using the tools provided by LiveView but this would be really helpful for people like me who are trying to build out reusable component libraries for LiveView.

Marked As Solved

steffend

steffend

Phoenix Core Team

You can:

  slot :inner_block, required: false

  def dropdown_menu_trigger(assigns) do
    if assigns.inner_block != [] do
      ~H"""
      <%= render_slot(@inner_block, %{"data-item" => "trigger"}) %>
      """
    else
      ~H"""
      <button data-item="trigger">Open</button>
      """
    end
  end
  def render(assigns) do
    ~H"""
    <div>
      Hello

      <.dropdown_menu_trigger />

      <.dropdown_menu_trigger :let={attributes}>
        <button {attributes}>Open with other content 🚀</button>
      </.dropdown_menu_trigger>
    </div>
    """
  end

Renders:

<div>
  Hello

  <button data-item="trigger">Open</button>

  <button data-item="trigger">Open with other content 🚀</button>
</div>

Where Next?

Popular in Proposals: Ideas Top

alaadahmed
Hi folks, I tried Phoenix 1.7 and it is awesome, but I have small suggestion, which in my opinion will organize files in a better way. ...
New
rmoorman
Current situation Currently, the structure of the HTML returned by phoenix is determined by the layouts (components/layouts/[root,app].ht...
New
PJUllrich
Hey folks, I have the unique problem that I need to ignore all “change” and “input” events for one specific input element in a LiveView F...
New
jakeprem
Goal: To make JS.patch and JS.navigate more interoperable with JS.push. Scenario: Imagine making a reusable Phoenix component and you wa...
New
GenericJam
I’ve been messing around with image generation models recently and thought this could be wrapped in a library or people could just use th...
New
woylie
We are seeing a lot of warning logs like this: navigate event to "https://someurl" failed because you are redirecting across live_sessio...
New
eagle-head
Hi everyone, I’ve been researching Content Security Policy Level 3 support in Phoenix and wanted to share my findings and a proposal for...
New

Other popular topics Top

electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 44139 214
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New

We're in Beta

About us Mission Statement