live_select icon indicating copy to clipboard operation
live_select copied to clipboard

no component found matching phx-target, when live_select uses push_navigate

Open Munksgaard opened this issue 10 months ago • 2 comments

LiveSelect and LiveView versions

$ mix deps | grep live_select
* live_select 1.5.4 (Hex package) (mix)
  locked at 1.5.4 (live_select) 4fa26776

$ mix deps | grep phoenix_live_view
* phoenix_live_view 1.0.4 (Hex package) (mix)
  locked at 1.0.4 (phoenix_live_view) a9865316

Describe the bug

When returning {:noreply, push_navigate(socket, to: "/bar")} from a handle_event on a form that uses live_select, I get this error message in my JS console:

utils.js:7 no component found matching phx-target of 1

Screenshot:

Image

Expected behavior

No error

Actual behavior

An error

Browsers

I have only been able to reproduce this error in Chrome/Chromium (Version 130.0.6723.69 (Official Build) (64-bit)).

Notably, it does not happen in Firefox (131.0.3 (64-bit)).

Issue Repo

https://github.com/Munksgaard/live_select_navigate_bug

The interesting code is in lib/phxtest_web/router.ex, notably the FooLive module which renders a form and has a handle_event. Module reproduced here for ease of reading:

defmodule FooLive do
  use Phoenix.LiveView

  import LiveSelect

  def mount(_assigns, _, socket) do
    {:ok, assign(socket, :form, to_form(%{"option" => ""}, as: :foo))}
  end

  def render(assigns) do
    ~H"""
    <.form for={@form} phx-change="change">
      <.live_select field={@form[:option]} options={[:bar, :baz]} />
    </.form>
    """
  end

  def handle_event("change", params, socket) do
    {:noreply, push_navigate(socket, to: "/bar")}
  end
end

To reproduce, simply start a server with mix phx.server, go to https://localhost:4000, click in the live_select and select any of the options. You'll see the error in the browser console log.

Additional context

It is not actually clear to me whether this is a live_select bug or a live_view bug. My hope was that, if this is indeed a live_view bug, someone in here might be able to help me produce an even more minimal reproducer. I haven't been able to reproduce the bug without using live_select.

I also know that we're using live_select in an unconventional manner, but I still feel like this should work.

Munksgaard avatar Feb 21 '25 07:02 Munksgaard

This is the function that's logging the error:

  withinTargets(phxTarget, callback, dom = document, viewEl){
    // in the form recovery case we search in a template fragment instead of
    // the real dom, therefore we optionally pass dom and viewEl

    if(phxTarget instanceof HTMLElement || phxTarget instanceof SVGElement){
      return this.liveSocket.owner(phxTarget, view => callback(view, phxTarget))
    }

    if(isCid(phxTarget)){
      let targets = DOM.findComponentNodeList(viewEl || this.el, phxTarget)
      if(targets.length === 0){
        logError(`no component found matching phx-target of ${phxTarget}`)
      } else {
        callback(this, parseInt(phxTarget))
      }
    } else {
      let targets = Array.from(dom.querySelectorAll(phxTarget))
      if(targets.length === 0){ logError(`nothing found matching the phx-target selector "${phxTarget}"`) }
      targets.forEach(target => this.liveSocket.owner(target, view => callback(view, target)))
    }
  }

Munksgaard avatar Feb 21 '25 07:02 Munksgaard

By setting liveSocket.enableLatencySim(1000), the error goes away. I suspect there is some kind of race between tearing down the LiveComponent inside the live_select and navigating to the new page.

Munksgaard avatar Feb 21 '25 07:02 Munksgaard