Patterns for handling form-level errors in LiveView 1.0

i’m working on upgrading our version of LiveView to 1.0. We have an old form that has a form-level error that is displayed in a div under the header.

I switched over our input error handling to use Phoenix.Component.used_input? and it works fine. We were setting a form-level error with an atom. This works, except that the error immediately shows up on the change event. Ideally it would only show up when the user clicks submit.

I guess this isn’t specifically about LiveView 1.0, but I’d like to know if there are established patterns for handling this level of error in the changeset.

This is the code for our error_tag function, which returns the element with the errors for display:

  def error_tag(%Phoenix.HTML.Form{} = form, field, ignore_used_input \\ false) when is_atom(field) do
    if field_errors?(form[field], ignore_used_input) do
      content_tag(:p, class: "help-block help is-danger", phx_feedback_for: input_name(form, field)) do
        translate_error(List.first(form[field].errors))
      end
    end
  end

The error would immediately show if you are validating on a phx-change event and re-assigning the form. If you want it to only show once the user hits submit then just move the validation call to the phx-submit handler. Although at that point your context create/update function will already handle it for you so you don’t really need to do anything more, just remove the validation on change.

Thanks for your reply. The way our change event validations currently work is that they run the changeset validations for the schema in question, including the one for this form level validation.

Is the idea that I run one set of changeset validations on the change event, and another on save with the extra validation?

It would really help to see some code as this makes it sound like you have two different levels of validation?

I’m saying that you don’t want to run validations on the change event if you only want to show errors when the user hits submit. I’m saying this assuming your submit event looks something like the following:

def handle_event("submit", %{"foo" => "foo_params"}, socket) do
  case MyApp.Foos.create_foo(foo_params) do
    {:ok, foo} ->
      # handle create
    {:error, changeset} ->
      {:noreply, assign(socket, :form, to_form(changeset)}
  end
end

The changeset return from Repo in the error case will have run validations.

In my previous answer I recommended just not having a change event, but that was a bit shortsighted since you will lose auto-form recovery in the event of a socket disconnect. Your change event should just create a new changeset without validation:

def handle_event("change", %{"foo" => "foo_params"}, socket) do
  changeset = MyApp.Foos.change_foo(foo_params)

  {:noreply, assign(socket, :form, to_form(changeset)}
end
1 Like

Apologies for my poor explanation. I want to show field-level errors according to the default behaviour where errors show up once the field has been touched. This is working well, and this is why I need the change event.

However, there are sometimes form-level errors that I only wanted to see on save. So I have two categories of errors.

The way it’s working right now in my branch where I’ve switched to LiveView 1.0 is that the form-level errors show up immediately, as soon as any field is touched (since that’s when the change even runs), and I was wondering if there is a convention for storing those and handling them. Our app is pretty large and has been around a long time so I’m trying to implement repeatable patterns that help us to not re-implement certain things on every form (if possible).

I like to have two changeset validations, one at the form level and one at the repo level. This also helps when the form inputs are different from the schema.

1 Like