Using a Surface.Component from a Phoenix.Component?

Hello,

We’re trying to slowly move away from Surface into using regular Phoenix components (HEEX).
However we wish to still be able to use some of the surface components we already have. Is there a way to do that?

I tried calling it as <.MySurfaceComponent.render /> from a Phoenix.Component, which works for simple surface components, but when we have more complex components, it throws an error.

For example this:

defmodule MyAppWeb.Components.TestComponent do
  use Phoenix.Component
  alias MyAppWeb.Surface.TestSurfaceComponent
  
  def test_component(assigns) do
    ~H"""
    <div>
      <div>lv_component</div>
      <.another_component />
      <TestSurfaceComponent.render myprop="myprop" />
    </div>
    """
  end

  def another_component(assigns) do
    ~H"""
    <div>
      another_component
    </div>
    """
  end
end

defmodule MyAppWeb.Surface.AnotherSurfaceComponent do
  use Surface.Component
  def render(assigns) do
    ~F"""
    <div>another surface component</div>
    """
  end
end

defmodule MyAppWeb.Surface.TestSurfaceComponent do
  use Surface.Component
  alias MyAppWeb.Surface.AnotherSurfaceComponent
  prop myprop, :string
  def render(assigns) do
    ~F"""
    <div>
      <div>surface_component {@myprop}</div>
      <AnotherSurfaceComponent />
    </div>
    """
  end
end

Crashes with:

[error] GenServer #PID<0.1365.0> terminating
** (KeyError) key :__context__ not found in: %{__changed__: nil, __caller_scope_id__: nil, myprop: "myprop"}
    (my_app 0.1.0) lib/my_app_web/components/test_component.ex:37: anonymous fn/2 in MyAppWeb.Surface.TestSurfaceComponent."render (overridable 1)"/1
    (my_app 0.1.0) /Users/ricardo/XUKU/my_app/lib/my_app_web/components/test_component.ex:10: MyAppWeb.Components.TestComponent.test_component/1
    (elixir 1.15.2) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:384: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:538: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
    (elixir 1.15.2) lib/enum.ex:2510: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:384: Phoenix.LiveView.Diff.traverse/7
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/diff.ex:136: Phoenix.LiveView.Diff.render/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/static.ex:252: Phoenix.LiveView.Static.to_rendered_content_tag/4
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/static.ex:135: Phoenix.LiveView.Static.render/3
    (phoenix_live_view 0.19.3) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
    (phoenix 1.7.6) lib/phoenix/router.ex:430: Phoenix.Router.__call__/5
    (my_app 0.1.0) deps/plug/lib/plug/error_handler.ex:80: MyAppWeb.Router.call/2
    (my_app 0.1.0) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2
    (my_app 0.1.0) deps/plug/lib/plug/debugger.ex:136: MyAppWeb.Endpoint."call (overridable 3)"/2
    (my_app 0.1.0) lib/my_app_web/endpoint.ex:1: MyAppWeb.Endpoint.call/2
    (phoenix 1.7.6) lib/phoenix/endpoint/sync_code_reload_plug.ex:22: Phoenix.Endpoint.SyncCodeReloadPlug.do_call/4
    (bandit 1.0.0-pre.10) lib/bandit/pipeline.ex:116: Bandit.Pipeline.call_plug/2
Last message: {:continue, :handle_connection}
State: {%ThousandIsland.Socket{socket: #Port<0.60>, transport_module: ThousandIsland.Transports.TCP, read_timeout: 60000, span: %ThousandIsland.Telemetry{span_name: :connection, telemetry_span_context: #Reference<0.3603920085.2461532163.4417>, start_time: -576460726155188000, start_metadata: %{remote_port: 58973, remote_address: {127, 0, 0, 1}, telemetry_span_context: #Reference<0.3603920085.2461532163.4417>, parent_telemetry_span_context: #Reference<0.3603920085.2461270018.260616>}}}, %{opts: %{websocket: [], http_1: [], http_2: []}, plug: {Phoenix.Endpoint.SyncCodeReloadPlug, {MyAppWeb.Endpoint, []}}, handler_module: Bandit.InitialHandler, http_1_enabled: true, http_2_enabled: true, websocket_enabled: true}}}
1 Like

Using Surface components in vanilla Phoenix templates is partially supported but usually not recommended

https://surface-ui.org/usage_with_phoenix_templates

1 Like