I have a LiveView component which uses form_for/3
with a changeset, in the assigns map.
When I store the changeset in the assigns map using the key name :guide_list
, there is an undefined FormData protocol error. However, if I change the key name to :changeset
or even :foo
, there is no error. The socket seems the same, with the exception of the different assigns key name. The assigns key value is an Ecto.Changeset in all situations.
Clearly, I do not understand something about Phoenix because I am surprised the key name matters. Can someone please point me to documentation about this?
Thanks for any help!
Here are the component, inspects of the socket, and the error:
This code will result in a Protocol error, because the assigns key name is :guide_list
:
defmodule CrisprWeb.GuideSourceComponent do
use CrisprWeb, :live_component
alias Crispr.GuideList
def render(assigns) do
~L"""
<%= f = form_for @guide_list, "#", phx_target: @myself %>
<%= textarea f, :guides %>
<%= error_tag f, :guides %>
<%= submit "Set guides" %>
</form>
"""
end
def mount(socket) do
{:ok, socket |> assign_guide_list |> IO.inspect(label: "GuideSourceComponent: mount socket")}
end
defp assign_guide_list(socket) do
socket |> assign(guide_list: %GuideList{} |> GuideList.changeset(%{}))
end
end
Changing these lines to rename the key will allow the form to render:
<%= f = form_for @foo, ...
...
socket |> assign(foo: %GuideList{} |> GuideList.changeset(%{}))
...
Here are the sockets from both cases:
GuideSourceComponent: mount socket: #Phoenix.LiveView.Socket<
assigns: %{
flash: %{},
guide_list: #Ecto.Changeset<
action: nil,
changes: %{},
errors: [guides: {"can't be blank", ...}],
data: #Crispr.GuideList<>,
valid?: false
>,
myself: %Phoenix.LiveComponent.CID{cid: 1}
},
changed: %{flash: true, guide_list: true},
endpoint: CrisprWeb.Endpoint,
id: "guides-selector",
parent_pid: #PID<0.502.0>,
root_pid: nil,
router: CrisprWeb.Router,
view: CrisprWeb.GuideSelectorLive,
...
>
GuideSourceComponent: mount socket: #Phoenix.LiveView.Socket<
assigns: %{
flash: %{},
foo: #Ecto.Changeset<
action: nil,
changes: %{},
errors: [guides: {"can't be blank", ...}],
data: #Crispr.GuideList<>,
valid?: false
>,
myself: %Phoenix.LiveComponent.CID{cid: 2}
},
changed: %{flash: true, foo: true},
endpoint: CrisprWeb.Endpoint,
id: "guides-selector",
parent_pid: #PID<0.1587.0>,
root_pid: #PID<0.1587.0>,
router: CrisprWeb.Router,
view: CrisprWeb.GuideSelectorLive,
...
>
Here is the error when the key name is :guide_list
:
[error] #PID<0.502.0> running CrisprWeb.Endpoint (connection #PID<0.501.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /
** (exit) an exception was raised:
** (Protocol.UndefinedError) protocol Phoenix.HTML.FormData not implemented for %Crispr.GuideList{guides: nil} of type Crispr.GuideList (a struct). This protocol is implemented for the following type(s): Ecto.Changeset, Plug.Conn, Atom
(phoenix_html 2.14.3) lib/phoenix_html/form_data.ex:1: Phoenix.HTML.FormData.impl_for!/1
(phoenix_html 2.14.3) lib/phoenix_html/form_data.ex:15: Phoenix.HTML.FormData.to_form/2
(phoenix_html 2.14.3) lib/phoenix_html/form.ex:325: Phoenix.HTML.Form.form_for/3
...snip