Phx-change on both form and input level

Let’s say there’s a typical form with phx-change="validate" on the form level and there are also some fields in the form, which have phx-change="autocomplete" on input level. The problem I experience is that form level change event is not triggered whenever input level one is.

What is the correct way of handling such situations?

2 Likes

Can you elaborate as to the purpose of these secondary phx-change settings? The form level one will trigger with every change on every input anyway.

Let’s try… The form-level change is used to validate the whole form and return errors if any. The input-level one is used to pass values for autocompleting a particular field. These are two different events for different purposes and on the input level I immediately know which field triggered the event as only its data is passed. All in all both are needed and only some (like 15% of all) fields need autocompletion while all of them need to pass validation. Therefore it looked natural to me to try triggering autocomplete only for those fields, which need it, while triggering validate on all changes. I could probably (?) use form-level for everything and parse-out changed field from the params somehow but I’d prefer to have those contexts separated unless there’s currently no way to make it work (why would that be?).

I think what you want is the "_target" value sent via phx-change to handle event. So if you set it at the form level, you can see which input changed via "_target" without any extra wiring.

2 Likes

Possibly, yes. And instead of validate and autocomplete as needed there’d have to be something combined like handle_change with extra block checking what to do with the event. I. e. whether it was triggered by input requiring autocompletion or not, extracting the value, etc. Doable, of course, even if seemingly far less elegant. Still - why can’t both levels work together? Is it by design for some reason(s)? Or because of some limitations somewhere? Or they actually can but I don’t know how to make them do?

The default generated validation callback just runs a params map through the resource changeset based off the resource struct in the socket assigns. This means it should handle a subset of fields just as well as all the fields. So why not directly call the validate callback from the autocomplete callback?

def handle_event("autocomplete", %{"field" => value} = params, socket) do
  # add autocomplete logic and then add result(s) to socket and/or params
  # then call the `validate` callback with the updated socket and/or params
  handle_event("validate", %{"resource" => params}, socket)
end

These excerpts – and the comments especially – seem relevant from a quick skim through live_socket.js.

And based off of the LiveView docs on form events, it seems like what you’re seeing is expected behavior.

In general, it is preferred to handle input changes at the form level, where all form fields are passed to the LiveView’s callback given any single input change…
You may wish for an individual input to use its own change event or to target a different component. This can be accomplished by annotating the input itself with phx-change, for example:

<.form for={@form} phx-change="validate" phx-submit="save">
  ...
  <.input field={@form[:email]}  phx-change="email_changed" phx-target={@myself} />
</.form>

Then your LiveView or LiveComponent would handle the event:

def handle_event("email_changed", %{"user" => %{"email" => email}}, socket) do
  ...
end

Note: only the individual input is sent as params for an input marked with phx-change.

I thought of this too, but this way it would currently break the flow, because params contain only the one, specific input’s data rather than the full form. I’d need to rewrite the rather complex validation handler

I read those docs and it wasn’t clear to me that it is form-level EOR input-level. Now, rereading this again and looking for hints, there is one word that can suggest it to be the case: “own change event” w/o any qualifiers, although still…

1 Like