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

Phoenix error when passing c &live_component/1 into a default slot #254

Open
evnp opened this issue Dec 14, 2024 · 6 comments
Open

Phoenix error when passing c &live_component/1 into a default slot #254

evnp opened this issue Dec 14, 2024 · 6 comments

Comments

@evnp
Copy link

evnp commented Dec 14, 2024

First off, want to say I've loved working with Temple – what an incredibly beautiful DSL for expressing template structure and logic! I'm not sure if the situation described below is intended to be supported or not; I realize Temple isn't tightly coupled with Phoenix.

Describe the bug
Passing c &live_component/1 into a default slot causes this error:

ArgumentError: cannot convert component TestAppWeb.TestLive.Components.TestLiveComponent with id "test-live-component" to HTML.

A component must always be returned directly as part of a LiveView template.

Passing a function component in the same way works fine.

Rendering with HEEx instead and passing the live component through a default slot also works fine.

To Reproduce
I have this live component:

defmodule TestAppWeb.Components.TestLiveComponent do
  use TestAppWeb, :live_component

  def render(assigns) do
    temple do
      div do: "TEST LIVE COMPONENT CONTENT"
    end
  end
end

and this "control" function component:

defmodule TestAppWeb.Components.TestFuncComponent do
  use TestAppWeb, :component

  def render(assigns) do
    temple do
      div do: "TEST FUNCTION COMPONENT CONTENT"
    end
  end
end

In a normal live view, I want to pass the live component into a standard Phoenix modal (from default generated core_components.ex):

defmodule TestAppWeb.TestLive do
  use TestAppWeb, :live_view

  def render(assigns) do
    temple do
      button "phx-click": show_modal("test-modal"), do: "Show test modal"
      c &modal/1, id: "test-modal" do
        c &live_component/1,
          id: "test-live-component",
          module: TestAppWeb.TestLive.Components.TestLiveComponent
      end
    end
  end
end

Trying to render this, Phoenix throws the error above:

cannot convert component TestAppWeb.TestLive.Components.TestLiveComponent with id "test-live-component" to HTML.

A component must always be returned directly as part of a LiveView template.

If I replace the live component with the functional component, things work as expected:

defmodule TestAppWeb.RealmsLive.Edit do
  use TestAppWeb, :live_view

  def render(assigns) do
    temple do
      button "phx-click": show_modal("test-modal"), do: "Show test modal"
      c &modal/1, id: "test-modal" do
        c &TestAppWeb.TestLive.Components.TestFuncComponent.render/1
        # c &live_component/1,
        #   id: "test-live-component",
        #   module: TestAppWeb.TestLive.Components.TestLiveComponent
      end
    end
  end
end
Screen.Recording.2024-12-14.at.12.12.31.PM.mov

If I replace the temple code in the parent live view with HEEx, things also work as expected, with the live component rendering successfully into the modal's default slot:

defmodule TestAppWeb.TestLive do
  use TestAppWeb, :live_view

  def render(assigns) do
    ~H"""
    <div>
      <button phx-click={show_modal("test-modal")}>Show test modal</button>
      <.modal id="test-modal">
        <.live_component
          id="test-live-component"
          module={CcWeb.RealmsLive.Components.TestLiveComponent}
        />
      </.modal>
    </div>
    """
  end
end
Screen.Recording.2024-12-14.at.12.19.03.PM.mov

Expected behavior

Ideally, this Temple code in a live view should successfully render a modal with content in the default slot from a live component:

defmodule TestAppWeb.TestLive do
  use TestAppWeb, :live_view

  def render(assigns) do
    temple do
      button "phx-click": show_modal("test-modal"), do: "Show test modal"
      c &modal/1, id: "test-modal" do
        c &live_component/1,
          id: "test-live-component",
          module: TestAppWeb.TestLive.Components.TestLiveComponent
      end
    end
  end
end
Screen Shot 2024-12-14 at 12 24 01 PM

Screenshots

Please see screenshots/recordings inlined above for clarity

Desktop (please complete the following information):

  • OS: MacOS Monterey (12.3)
  • Temple Version {:temple, "~> 0.14.0"}
  • Elixir Version
  • Erlang Version
$ elixir --version
Erlang/OTP 26 [erts-14.2.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit] [dtrace]

Elixir 1.17.2 (compiled with Erlang/OTP 26)

Additional context

I imagine live components represent a complex set of factors to integrate well with – my impression overall is that even the Phoenix docs recommend avoiding them if at all possible in favor of simpler function components. I'm just wondering if this is a use case that Temple intends to support? It feels like a rough edge at the moment (I can work around it with an intermediate modal component that uses HEEx instead of Temple, but that's not ideal).

@evnp evnp changed the title Error when passing c &live_component/1 into a default slot Phoenix error when passing c &live_component/1 into a default slot Dec 14, 2024
@mhanberg
Copy link
Owner

Thank you for the thorough big report!

Can you share your temple config?

@evnp
Copy link
Author

evnp commented Dec 15, 2024

Thanks for taking a look at this @mhanberg. It was tough to describe with any brevity!

For configuring Temple in this Phoenix project, I have these entries in mix.exs:

defmodule Cc.MixProject do
  use Mix.Project

  def project do
    [
      app: :cc,
      ...
      compilers: [:temple] ++ Mix.compilers(),
      ...
    ]
  end

  defp deps do
    [
      ...
      {:temple, "~> 0.14.0"},
      ...
    ]
  end
end

this line at the bottom of config/config.exs:

config :temple, engine: Phoenix.LiveView.Engine

and these lines in lib/cc_web.ex:

defmodule CcWeb do
  ...
  defp html_helpers do
    quote do
      ...
      import Temple
      import Phoenix.LiveView.TagEngine, only: [component: 3, inner_block: 2]
    end
  end
end

(probably obvious, but the html_helpers lines are indirectly use'd by all live views, function components, and live components, so those lines should be included in all those contexts through something like use CcWeb, :live_view which in turn calls unquote(html_helpers()), standard Phoenix setup as far as I'm aware)

I had run into this #201, so a few of these things were pulled from the instructive PR linked there (georgevanderson/temple_liveview#1). But I'll be the first to admit I'm lacking in context/knowledge here – can't say I have a strong understanding of the purpose behind each line above.

@mhanberg
Copy link
Owner

What version of LiveView are you using?

@evnp
Copy link
Author

evnp commented Dec 15, 2024

Sorry - should have included the whole file, or even better, made a public repo. Did that now: https://github.com/evnp/phoenix-sandbox/blob/main/mix.exs

Current version of LiveView (which I should update, by the looks of it) is:

      {:phoenix_live_view, "~> 1.0.0-rc.6", override: true},

@evnp
Copy link
Author

evnp commented Dec 15, 2024

I've been meaning to figure out single-file phoenix apps with phoenix-playground. Using that, have put together some minimal examples in case they're helpful:

Repro of the issue (live component rendered with Temple inside a modal):
https://livebook.dev/run/?url=https://github.com/evnp/phoenix-sandbox/blob/main/livebooks/minimal-bug-repro-temple-livecomponent-example.livemd

Working version (live component rendered with Temple outside of a modal):
https://livebook.dev/run/?url=https://github.com/evnp/phoenix-sandbox/blob/main/livebooks/minimal-working-temple-livecomponent-example.livemd

@mhanberg
Copy link
Owner

I think there might be some kind of optimization that HEEx is doing compared to what temple does.

for both, when you compile a component, it ends up being a call to a "component/3" function, and if you have slots, they are basically like function clauses.

so in your example, the live component is surrounded by a function, which is what the docs say you can't do.

heex does the same thing tho, which leads me to believe they must have something that declares its a "tag" (ie component) and not a normal function call.

i'll investigate with the live view team

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

2 participants