How to display changeset errors for required `embeds_many/3` with `inputs_for/1`

I have two Ecto schemas: Project and Translation.

A project can have multiple labels (Translations) to support multilingual input:

defmodule Project do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: false}
  embedded_schema do
    embeds_many :labels, Translation, on_replace: :delete
  end

  @doc false
  def changeset(project, attrs) do
    project
    |> cast(attrs, [:id])
    |> validate_required([:id])
    |> cast_embed(
      :labels,
      sort_param: :labels_sort,
      drop_param: :labels_drop,
      required: true
    )
  end

I added an input_for/1 in my template, following the docs:

(...)
<div>
  <label>Labels</label>
  <.inputs_for :let={ef} field={@form[:labels]}>
    <input type="hidden" name="project[labels_sort][]" value={ef.index} />
    <.input type="text" field={ef[:language]} placeholder="language" />
    <.input type="text" field={ef[:text]} placeholder="text" />
    <label>
      <input type="checkbox" name="project[labels_drop][]" value={ef.index} class="hidden" />
      <.icon name="hero-x-mark" class="w-6 h-6 relative top-2" />
    </label>
  </.inputs_for>

  <label class="block cursor-pointer">
    <input type="checkbox" name="project[labels_sort][]" class="hidden" />
    add more
  </label>

  <input type="hidden" name="project[labels_drop][]" />
</div>
(...)

The one aspect where I deviate from the docs is the :required and that is also what I struggle with:

If i do not add any label and hit “save”, the changeset correctly evaluates an error (labels: {"can't be blank", [validation: :required]}), but there is no feedback in the interface (except the fact that I am still in the form).

All other errors get displayed (missing id for the project or missing language or text for the Translation).

Ok, by adding the following I now get the feedback:

<label>Lables</label>          
<div phx-feedback-for={@form[:labels].name}>
  <.error :for={{msg, _} <- @form[:labels].errors}><%= msg %></.error>
</div>
(...)

But now it gets displayed as soon as I type in a project id. Looks like it gets evaluated as having received user input. Any way to prevent this?

Are you asking about rate limiting? Setting the debounce on the field will add a delay. Also, not having a phx-change event evaluate, i.e. only checking on submit.

https://hexdocs.pm/phoenix_live_view/bindings.html#rate-limiting-events-with-debounce-and-throttle

I’d just start with a changeset without errors and not depend on the input dirty tracking LV does on the client. There’s no “input” for the whole collection anyways.

@LostKobrakai / @f0rest8

You both mean: I could remove phx-change="validate" from my form, correct? Problem: Then the whole input_for/1 logic concerning dynamically adding and removing entries stops working.

Edited as I re-read where you type in the :id to say I’m not sure. But the Phoenix Trello example does have a :required field on the :title.

In the Phoenix Trello example :title is the required field and there’s a corresponding input field for the user to provide the title.

To translate my problem to the Trello example:

In my case I’d have a :required on the :notifications in the list schema. The interface feedback works without any problems for both missing :title in List and missing :email in EmailNotification etc…

The only case I struggle is the error when :notifications is an empty list.

Do you have a separate changeset function for the embed? e.g.

cast_embed(:profile, required: true, with: &profile_changeset/2)

Yes, but I have no problem with the error evaluation itself. I just struggle with displaying the error correctly within my live view.

Ah, okay. How do you want the error to display again? Can you send a screenshot?

I’ve attached an image below from new feature I’m adding to Metamorphic and the error displays after it fires the validation event: