Phx-change not fired inside LiveView

I converted a LiveComponent to a LiveView in order to support an async Task. When my code was in a LiveComponent, my phx-change fired for my input, and my phx-click fired for my button. Now that the code’s inside a LiveView, I deleted the phx-target={@myself} from both fields and the input event has gone silent. I still get the phx-click from the button, but don’t even see any messages about unhandled events for the input. Why does input work in LiveComponent but not LiveView?

def render(assigns) do
  ~H"""
  <div>
    <input value={@query} name="query" phx-change="change_query" />
    <button phx-click="search" type="button">Search</button>
  </div>
  """
end

def handle_event("change_query", %{"query" => query}, socket) do
  {:noreply, assign(socket, query: query)}
end

def handle_event("search", %{}, socket) do
  {:noreply, assign(socket, loading: true)}
end

is this inside a form? phx-change events are only emitted when inside a form AFAIK.

1 Like

Yes, it’s inside a form. Though that form is higher up in the hierarchy so maybe that’s messing things up?

LiveView
|- LiveComponent with form
   |- LiveComponent
      |- LiveView w/ problemantic input

This same hierarchy worked when that last LiveView was a LiveComponent

Mmh… that looks like quite a complex hierarchy of views and components. I haven’t checked, but I suspect that a form spread across 2 LiveViews (and thus 2 processes) might cause problems. I would probably try to simplify and make sure you’re dealing with only 1 LiveView.

I’d love to just have one LiveView, but sadly handle_info isn’t supported by LiveComponent. So to have a self-contained component that can do async work, I had to convert it to a LiveView. It’s definitely ugly since LiveComponent and LiveView are declared and pass values differently, but I’m not sure what else to do.

An other option is to use tasks and send_update for asynchronous work in live components.

https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#send_update/3

2 Likes

Oh wow- yeah that worked. I’m not sure why I was finding much more complex solutions. The last part that tripped me up is I had to follow the docs precisely and call self() outside of my task. Putting it inside the task returned some other value that didn’t track back to my component:

def handle_event("search", %{"query" => query}, socket) do
  pid = self()

  Task.start(fn ->
    {:ok, results} = Ido.Media.PhotoApi.search(query)
    send_update(pid, __MODULE__, id: socket.assigns.id, search_results: results)
  end)

  {:noreply, assign(socket, loading: true)}
end

Thanks everyone!

2 Likes