Repository navigation
Phoenix LiveView form does not show errors on polymorphic_embeds_many fields #113
Description
Hi and thanks for the great work !
I'm trying to create a form for a schema which has polymorphic_embeds_many field using polymorphic_embed_inputs_for but the validation errors are not shown on the inner forms when I use the base input core component.
Below is a minimum example using the schemas defined in the documentation (MyApp.Channel.Email and MyApp.Channel.SMS are omitted for brevity) and the code for my LiveView.
Inputing invalid values in the inner fields does not show any error while it does for the outer field. Am I doing something wrong with the way I build my form ?
After looking around a bit, I see that the errors are properly set on the inner fields but they are not displayed and this is related to the use of Phoenix.Component.used_input?/1 in the input core component. I was able to make it work by modifying a few lines of to_form in PolymorphicEmbed.HTML.Helpers but I'd like to know if I am properly constructing my form before going there.
defmodule MyApp.Reminder do
use Ecto.Schema
import Ecto.Changeset
import PolymorphicEmbed
schema "reminders" do
field :date, :utc_datetime
field :text, :string
polymorphic_embeds_many :channels,
types: [
sms: MyApp.Channel.SMS,
email: MyApp.Channel.Email
],
on_type_not_found: :raise,
on_replace: :delete
end
def changeset(struct, values) do
struct
|> cast(values, [:date, :text])
|> cast_polymorphic_embed(:channels, required: true)
|> validate_required(:date)
end
enddefmodule PolembedWeb.ReminderLive.Index do
use PolembedWeb, :live_view
import PolymorphicEmbed.HTML.Helpers
alias MyApp.Reminder
@impl true
def mount(_params, _session, socket) do
form =
Reminder.changeset(%Reminder{}, %{
"channels" => [
%{"__type__" => "sms", "number" => "32"},
%{"__type__" => "email"}
]
})
|> to_form()
{:ok, assign(socket, form: form)}
end
@impl true
def handle_event("validate", %{"reminder" => reminder_params}, socket) do
form = Reminder.changeset(%Reminder{}, reminder_params) |> to_form(action: :validate)
{:noreply, assign(socket, form: form)}
end
@impl true
def handle_event("save", _params, socket) do
{:noreply, socket}
end
@impl true
def render(assigns) do
~H"""
<.form for={@form} id="reminders-form" phx-change="validate" phx-submit="save">
<.input field={@form[:date]} type="date" label="Name" />
<.polymorphic_embed_inputs_for :let={channel} field={@form[:channels]}>
<%= case source_module(channel) do %>
<% MyApp.Channel.SMS -> %>
<.input field={channel[:number]} type="number" label="Number" />
<% MyApp.Channel.Email -> %>
<.input field={channel[:address]} type="text" label="Address" />
<% end %>
</.polymorphic_embed_inputs_for>
</.form>
"""
end
end
Activity
nduitz commentedon Dec 5, 2024
@anjou-low Hey, i can confirm the issue with
Phoenix.Component.used_input?/1.Could you elaborate what changes you did to
to_formto make it work?JacobAlexander commentedon Jun 23, 2025
bump
anjou-low commentedon Jul 28, 2025
The issue seems to come from the fact that
Phoenix.Component.used_input?/1looks at the field'sparamsto infer if it has been used but they are not always set correctly here:polymorphic_embed/lib/polymorphic_embed/html/helpers.ex
Line 74 in 18a506d
If I construct the form like this
then
but as soon as the params are sent by the client we get something like
and this format is not handled by
PolymorphicEmbed.HTML.Helpers.to_form/4.One quick fix could be to not
List.wrapthe changeset's params and do something like thatbut it feels hacky.
I think this is related to #74 because the params are properly handled by
cast_polymorphic_embed/2but we can't always rely on this since the changeset is transformed to a struct if it valid.
nduitz commentedon Aug 20, 2025
Hey,
I implemented something like you suggested in #132
Sadly this will introduce other problems:
when adding inputs using the
sort_paramit will populate prior params if you prepend new inputs.If I find the time I will try to dig a bit deeper