Unexpected behaviour of phx-click-away inside LiveComponent

I have a sample LiveView app with 2 LiveViews and 1 LiveComponent. LiveView 1 is live_rendered from the app layout so that it appears on every page (kinda like a sidebar). LiveView 2 is displayed when the page is routed to. LiveView 2 has a LiveComponent embedded inside it. The LiveComponent has a phx-click-away handle and a phx-target of @myself on it.

When my click is outside the LiveComponent but my click is inside LiveView 2, the click-away event fires, and the handle_event is called. However when I click on LiveView 1, the browser console records the error: no component found matching phx-target of 1, and the handle_event for the click-away is never called. I am trying to understand the behavior of phx-click-away, when it is inside a live-component, since this is unexpected. If the phx-click-away were inside LiveView 2, then it would fire when I clicked on LiveView 1.

How can I replicate the same behaviour of phx-click-away when it is fired on a LiveComponent, (especially when the LiveComponent is something like a dropdown that manages its own state, and needs phx-target={@myself})?

Here is some sample code to showing changes I made to reproduce this from the starter phoenix project.

router.ex:

... rest of router code
live "/", SecondLive
...

app.html.heex:

<main class="flex h-screen">
  <%= live_render(@socket, ClickawayWeb.FirstLive, id: "first", sticky: true) %>
  <.flash_group flash={@flash} />
  <%= @inner_content %>
</main>

live/first_live.ex:

defmodule ClickawayWeb.FirstLive do
  use ClickawayWeb, :live_view

  @impl true
  def mount(_params, _session, socket) do
    {:ok, socket, layout: false}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div class="bg-blue-300 flex-1 p-4 h-full">
      First (LiveView embedded in layout)
    </div>
    """
  end
end

live/second_live.ex:

defmodule ClickawayWeb.SecondLive do
  use ClickawayWeb, :live_view

  @impl true
  def render(assigns) do
    ~H"""
    <div class="bg-red-300 flex-1 p-4">
      Second  (LiveView on page) <.live_component module={ClickawayWeb.ThirdLive} id="third" />
    </div>
    """
  end
end

live/third_live.ex:

defmodule ClickawayWeb.ThirdLive do
  use ClickawayWeb, :live_component

  @impl true
  def handle_event("clicked-away-third", _, socket) do
    IO.inspect("clicked-away-third")
    {:noreply, socket}
  end

  @impl true
  def render(assigns) do
    ~H"""
    <div class="bg-green-300 flex-1 p-4" phx-click-away="clicked-away-third" phx-target={@myself}>
      Third (LiveComponent in second)
    </div>
    """
  end
end

In dispatchClickAway, we find all elements with phx-click-away, then map over them calling, withinOwners of the event target (which is the sticky view since we clicked in that),

Where withinOwners calls to owner

Owner calls closest view on the element (which is your sticky view since we pass in e.target), so owners <- withinOwners returns the stickyview and we then push the click event to the server with the sticky view + the component element, but since the component does not exist in the sticky view we see the error.

Probably you could just write

this.withinOwners(el, view => {

As I think we would only want to dispatch the event to the phx-click-away owner, not the click target owner?

I will post an issue I guess.

1 Like