Pattern matching on a LiveComponent?

Is there any way of determining (via pattern match or otherwise) if a component is a Phoenix.Component or a Phoenix.LiveComponent?

I have a Component which renders another component:

~H"""
         <div>
               ...
                <%= component(@form.component, %{changeset: @changeset}) %>
              ...
         </div>
"""

I realized there are some instances where I’d like to use this component but it would require passing in a LiveComponent. Something like this psudo code:

<%= if %Phoenix.LiveComponent{} = @form.component do %> 
        <%= live_component(@form.component, %{changeset: @changeset}) %>
<% else %>
        <%= component(@form.component, %{changeset: @changeset}) %>
<% end %>

For further context: I’m trying to create a reusable table component that allows for inline editing, so each row has an edit button, when clicked the parent LiveView sends a changeset for the row in question and the content of that row changes to a form component.
This is going to be used throughout an admin dashboard and will contain forms of various complexity, some are simple components controlled by their parent views but some are more complex LiveComponents.
I think that passing a component to a component feels a bit Reacty which I’m not a huge fan of, but in this case I think the React pattern could be fairly elegant. Although I’d be interested to hear any opposing viewpoints on that.

I don’t know if this is the most elegant approach but I realized that I had to pass Phoenix.Components in like a function: &component/1 while the LiveComponent can be passed in as the Module so I just ended up using is_function(component) and then calling either component/2 or live_component/2 inside an if statement.

I don’t see why you couldn’t pattern match on components the way you suggested. Just use a case or multiple function heads instead of if else

But unless I’m missing something, it would seem to make more sense to use slots for this kind of functionality.

1 Like

I assume when you say “match on components” the case statement would then list every component that could be passed in? That’s not reusable. I’m sorry if the psudo code in my original question was confusing, when I showed the pattern match I wasn’t referring to a specific component but wondering if it would be possible to match (or otherwise identify) Phoenix.LiveComponent broadly not just specific components.

I’ll give slots another look, I looked over them briefly on Friday and opted not to use them here but I don’t know why. Now that you mention it this does seem to be a good application for them.

I meant matching on Phoenix.LiveComponent and Phoenix.Component.

case @form.component do
  Phoenix.LiveComponent{} -> live_component(@form.component, %{changeset: @changeset})
  Phoenix.Component{} -> component(@form.component, %{changeset: @changeset})
end

You could even make a helper function for this if you don’t want the case in the template itself.

1 Like

Although that’s exactly what I’m looking for, I don’t think you can pattern match on those: Phoenix.Component.__struct__/0 is undefined, cannot expand struct Phoenix.Component.

(BTW I think I’m going to go with the slot approach as that’s a more established/preferred pattern).

Oh, sorry, I got confused. Yeah, function components are really just functions, I should have realized.

1 Like

The docs mention trying to limit conditionals in templates. There is a place for them, as well as for, but this doesn’t seem to be it to me or specifically it can get awkward to work with.

Slots seem easier as you can always have empty or optional slots that just don’t display anything or a

that doesn’t render. It “feels weird” but lots of conditional logic in the template feels more weird to me.

I wonder if you could use module names in assigns because you could pattern match on that. You may have to construct them or use case statements in handlers because you’d do something like _ <> LiveComponent and would have to prefix or postfix the type to the name. You could alternatively add a manual type as assigns but that seems like way more work for something that should match more easily without jumping through these hoops.

I haven’t seen a pattern like this when I scoured LiveBeats or Livebook (I haven’t seen TodoTrek) but I also haven’t been looking for it. I mention those because those are applications where I’ll see these ideas translate from docs to implementation.

1 Like