no component found matching phx-target, when live_select uses push_navigate
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:
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.
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)))
}
}
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.