Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic Functional Components #3597

Open
kyleboe opened this issue Dec 22, 2024 · 5 comments
Open

Dynamic Functional Components #3597

kyleboe opened this issue Dec 22, 2024 · 5 comments

Comments

@kyleboe
Copy link

kyleboe commented Dec 22, 2024

Per José's recommendation, let's start the discussion on dynamic functional components.

Similar to .live_component/1, I attempted build a non-live/functional component in which you can dynamically pass a module/function with slots to dynamically render in the view.

<.component
  module={@some_dynamic_module}
  function={@fn_name_as_atom}
>
  Stuff in the inner_block slot
  <:named_slot>
    Stuff in the named slot
  </:named_slot>
</.component>

The solution I came up with uses the Phoenix.LiveView.TagEngine.component/3 function to handle passing/applying the slots to the underlying functional component.

def component(assigns) do
  Phoenix.LiveView.TagEngine.component(
    &apply(assigns.module, assigns.function, [&1]),
    assigns,
    {__ENV__.module, __ENV__.function, __ENV__.file, __ENV__.line}
  )
end

Should something like this be added to LiveView?

@SteffenDE
Copy link
Collaborator

I'm not sure why slots would be a problem?

Using

def component(assigns) do
  {mod, assigns} = Map.pop(assigns, :module)
  {func, assigns} = Map.pop(assigns, :function)

  apply(mod, func, [assigns])
end

as

~H"""
<.component module={__MODULE__} function={:foo} any-other="assign">
  Hello from inner_block!
  <:named_slot>Hello from named slot!</:named_slot>
</.component>
"""

for example with

def foo(assigns) do
  ~H"""
  <h1>I am foo</h1>
  <%= render_slot(@inner_block) %>
  <%= render_slot(@named_slot) %>
  """
end

works just fine.

I'm open to having this included in LiveView, but maybe we just need to document how to do it? @josevalim

@josevalim
Copy link
Member

That was my thought as well. And apply wrapped in a component would suffice but, at the same time, is that enough to warrant something out of the box?

@SteffenDE
Copy link
Collaborator

It may also be better to implement this inside of one's app and then also define which slots are expected to work. So if you'd want to use the dynamic components that all have a :named_slot, define a

# shared attributes
attr :foo, ...

# shared slots
slot :named_slot, required: true

def my_component_with_named_slot(assigns) do
  {mod, assigns} = Map.pop(assigns, :module)
  {func, assigns} = Map.pop(assigns, :function)

  apply(mod, func, [assigns])
end
"""

to get proper compile time warnings? I think most of the time you'd have some kind of contract which slots and attributes you expect to have.

Having a .component built-in without any requirements might tempt people to not do this, which might not be what we want.

@josevalim
Copy link
Member

That's a very good point. Can you expand more on your use cases @kyleboe?

@kyleboe
Copy link
Author

kyleboe commented Dec 22, 2024

Having a .component built-in without any requirements might tempt people to not do this, which might not be what we want.

Yeah that makes a lot of sense. Definitely want to steer people towards those compile-time warnings.

Also, your example works perfectly for me so I'll spare you the additional use-case examples. The problems I ran into initially were related to my incorrect usage of apply/3 with assigns so apologies for my mistake there.

With the above discussion in mind, the only argument I could make for adding something like this is to provide a similar API to .live_component for those like myself where using apply/3 is less obvious. Although, as stated, that could be done via documentation as well.

I'm happy to open a PR for documentation or code either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants