phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
5.99k stars 902 forks source link

JS.dispatch within Form throws TypeError #3328

Closed PJUllrich closed 6 days ago

PJUllrich commented 1 week ago

Environment

Actual behavior

I try to force anphx-change event using a button from within a Phoenix.HTML.Form, but when I call a JS.dispatch("input", to: "#some-form") from inside a <.form id="some-form" ... > tag, the console shows a JS exception:

TypeError: Cannot read properties of undefined (reading 'getAttribute')
    at pe.targetComponentID (phoenix_live_view.min.js:11:19912)
    at pe.pushInput (phoenix_live_view.min.js:11:21225)
    at phoenix_live_view.min.js:11:2515
    at phoenix_live_view.min.js:11:9258
    at Ke.owner (phoenix_live_view.min.js:17:5817)
    at pe.withinTargets (phoenix_live_view.min.js:11:9247)
    at Object.exec_push (phoenix_live_view.min.js:11:2367)
    at phoenix_live_view.min.js:11:1897
    at Array.forEach (<anonymous>)
    at phoenix_live_view.min.js:11:1868

You can reproduce this using this little script:

Mix.install([
  {:liveview_playground, "~> 0.1.8"}
])

defmodule PageLive do
  use LiveviewPlaygroundWeb, :live_view

  alias Phoenix.LiveView.JS

  def mount(_params, _session, socket) do
    form = to_form(%{"number" => 0}, as: :input)
    {:ok, assign(socket, :form, form)}
  end

  def render(assigns) do
    ~H"""
    <.form :let={form} id="input-form" for={@form} phx-change="validate">
      <input id={form[:number].id} name={form[:number].name} value={form[:number].value} type="number" />
      <button type="button" phx-click={JS.dispatch("input", to: "#input-form")}>Dispatch</button>
    </.form>
    """
  end

  def handle_event("validate", %{"input" => params}, socket) do
    dbg(params)
    {:noreply, socket}
  end
end

LiveviewPlayground.start()

Save the script to e.g. bug.exs and execute it with elixir bug.exs. You'll see that the "change" event works well if you change the number input, but if you click on the button, you'll see the JS exception in the browser console.

This is likely related to https://github.com/phoenixframework/phoenix_live_view/issues/2916 but in this case, the input is actually wrapped by a proper <.form> tag and uses a Phoenix.HTML.Form under the hood. I also replicated this bug with an Ecto.Changeset as a source for the Phoenix.HTML.Form. Same bug happened.

Expected behavior

Clicking the button should cause the form to emit a validate event and send the currently filled-in parameters to the LiveView, just like any other input would.

harmon25 commented 6 days ago

My understanding is that the dispatch should be emitted from the input element and not the form itself.

The following adjustment works:

<button type="button" phx-click={JS.dispatch("input", to: "##{form[:number].id}")}>Dispatch</button>

But maybe it should also work at the form level 🤷

PJUllrich commented 6 days ago

True, that works for me too! Interesting 🤔 I'll keep this issue open for now, but that might be the answer indeed 👍

SteffenDE commented 6 days ago

@PJUllrich dispatching the event to the input element is the way to go. LiveView only expects input events on actual inputs (i.e. input, select, textarea). I also created a PR for a more useful error message. With it, the error would have been:

Error: dispatching a custom input event is only supported on input elements inside a form

which is hopefully more helpful :)