(Protocol.UndefinedError) protocol Phoenix.HTML.FormData not implemented for... This protocol is implemented for the following type(s): Ecto.Changeset, Atom, Plug.Conn

Hello there, I looked up a topic with a similar error and they recommended to write “rm -rf _build” and recompile, but this did not help me, so I am creating a new topic.
I have two schemas: “users” and “users_details”, which have 1:1 relations.
And my LiveView template is:

<%= form_for @user_changeset, "#",
[phx_change: :validate, phx_submit: :save, phx_hook: "SavedForm"], fn f-> %>

<%= label f, :email %>
<%= text_input f, :email, phx_debounce: "blur" %>
<%= error_tag f, :email %>

<%= inputs_for f, :users_details, fn f2 -> %>
<%= label f2, :first_name %>
<%= text_input f2, :first_name, phx_debounce: "blur" %>
<%= error_tag f2, :first_name %>
<% end %>

<div>
<%= submit "Save", phx_disable_with: "Saving..." %>
</div>

<% end %>

In my mount function i get user by session token and pass into socket user_changeset (with preloaded users_details) and user struct

  def mount(_params, %{"user_token" => user_token}, socket) do
    user =
      Accounts.get_user_by_session_token(user_token)
      |> Repo.preload(:users_details)

    user_changeset =
      user
      |> User.changeset(_params)

    {:ok,
     assign(socket, %{
       user_changeset: user_changeset,
       user: user,
     })}
  end

Here’s my handle_event save function:

  def handle_event("save", %{"user" => params}, socket) do
    {_, cset} =
      socket.assigns.user
      |> User.changeset(params)
      |> Ecto.Changeset.cast_assoc(:users_details, with: &UserDetails.changeset/2)
      |> Repo.update()

    {:noreply, assign(socket, user_changeset: cset)}
  end

And I really don’t understand what’s wrong, because the value in the database and in the template changes, but the error occurs in the terminal

[debug] QUERY OK db=0.1ms idle=1167.0ms
begin []
[debug] QUERY OK db=0.6ms
UPDATE "users" SET "email" = $1, "updated_at" = $2 WHERE "id" = $3 ["newEmail@gmail.com", ~N[2021-10-05 08:30:20], 1]
[debug] QUERY OK db=0.6ms
UPDATE "users_details" SET "first_name" = $1, "updated_at" = $2 WHERE "id" = $3 ["newFirstName", ~N[2021-10-05 08:30:20], 1]
[debug] QUERY OK db=19.2ms
commit []
[error] GenServer #PID<0.6447.0> terminating
** (Protocol.UndefinedError) protocol Phoenix.HTML.FormData not implemented for #Project.Accounts.User<__meta__: #Ecto.Schema.Metadata<:loaded, "users">, confirmed_at: nil, email: "newEmail@gmail.com", id: 1, users_details: %Project.Accounts.UserDetails{__meta__: #Ecto.Schema.Metadata<:loaded, "users_details">, first_name: "newFirstName", id: 1, inserted_at: ~N[2021-10-05 07:57:36], user_id: 1, ...> of type Project.Accounts.User (a struct). This protocol is implemented for the following type(s): Ecto.Changeset, Atom, Plug.Conn

Why does GenServer think I am passing the User structure when I explicitly call Ecto.changeset? And why is there no error in the template itself and everything is displayed correctly?

Thank you in advance.

Repo.update has the following spec:

update(changeset :: Ecto.Changeset.t(), opts :: Keyword.t()) ::
  {:ok, Ecto.Schema.t()} | {:error, Ecto.Changeset.t()}

Based on the terminal output, I’m guessing the error happens after the save event. So I’m guessing cset is a User struct at that point, and you’re assigning that struct to user_changeset, after which liveview picks up the change and attempt to load the form.

Thanks for your reply. I see that Repo.update returns

{:ok, Ecto.Schema.t()}

And in my case, cset has Ecto.Schema type. But how can i return a changeset into socket? If I put

cset |> User.changeset()

after Repo.update(). It doesn’t work for some reason even though User.changeset should return changeset.

Do you store the result of this action?

cset = case Repo.update(...) do
    {:ok, user} -> User.changeset(user)
    {:error, changeset} -> changeset
end
1 Like