How to create custom text input in live component phoenix 1.6?

Hello there! I have a post, generated with
mix phx.gen.live Blog Post body:string title:string
and tags, which have M:M relations with posts.
I add tags field at the template to create a new post with tags
I decide to add custom HTML form tags_input because if I use a text_input, I get and formatting error

lists in Phoenix.HTML and templates may only contain integers representing bytes, binaries or other lists, got invalid entry: %Project.Blog.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 3, inserted_at: ~N[2021-11-08 05:28:01], name: "tag5", updated_at: ~N[2021-11-08 05:28:01]}

I found a solution from a person with the nickname rawcode, but he used an older version of Phoenix and he added this code to post_view.ex

  def tags_input(form) do
    tags_string =
      form
      |> input_value(:tags)
      |> Enum.map(&tag_to_string/1)
      |> Enum.join(", ")

    text_input(form, :tags, value: tags_string)
  end

  def tag_to_string(%Ecto.Changeset{} = tag) do
    Ecto.Changeset.get_field(tag, :name)
  end

  def tag_to_string(%Tag{} = tag) do
    tag.name
  end

My template is presented as a live component (form_component.html.heex) and I put this code in form_component.ex. When I try to create post, I get an error:

[error] GenServer #PID<0.577.0> terminating
** (Protocol.UndefinedError) protocol Enumerable not implemented for "" of type BitString
    (elixir 1.12.2) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir 1.12.2) lib/enum.ex:141: Enumerable.reduce/3
    (elixir 1.12.2) lib/enum.ex:3952: Enum.map/2

When I try to edit a post with existing tags (tag5,tag6) , I get an error:

[error] GenServer #PID<0.600.0> terminating
** (Protocol.UndefinedError) protocol Enumerable not implemented for "tag5, tag6 " of type BitString
    (elixir 1.12.2) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir 1.12.2) lib/enum.ex:141: Enumerable.reduce/3
    (elixir 1.12.2) lib/enum.ex:3952: Enum.map/2

Why does this error occur? I put the tags_string variable into the console and got “tag5, tag6” - is a string, which matches the content. But when I try to add something else to the tags field an error occurs.

I hope I have explained my problem clearly. Thank you in advance!

I would suspect that either your input_value(form, :tags) returns a string or you don’t transform your input string to a list (in case :tags is a list).
But it is hard to analyse without more info.

My input_value(form, :tags)
returns

[
  %Project.Blog.Tag{
    __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
    id: 3,
    inserted_at: ~N[2021-11-08 05:28:01],
    name: "tag5",
    updated_at: ~N[2021-11-08 05:28:01]
  },
  %Project.Blog.Tag{
    __meta__: #Ecto.Schema.Metadata<:loaded, "tags">,
    id: 4,
    inserted_at: ~N[2021-11-08 05:28:01],
    name: "tag6",
    updated_at: ~N[2021-11-08 05:28:01]
  }
]

And after Enum.map and Enum.join, I inspect tags_string and get “tag5, tag6”

    tags_string =
      form
      |> input_value(:tags)
      |> Enum.map(&tag_to_string/1)
      |> Enum.join(", ")

      IO.inspect(tags_string) # "tag5, tag6"

But when I try to change the value of the tags field in the template (e.g. add “a” symbol to existing field value), I get an error:

[error] GenServer #PID<0.854.0> terminating
** (Protocol.UndefinedError) protocol Enumerable not implemented for "tag5, tag6a" of type BitString
    (elixir 1.12.2) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir 1.12.2) lib/enum.ex:141: Enumerable.reduce/3
    (elixir 1.12.2) lib/enum.ex:3952: Enum.map/2
    (Project 0.1.0) lib/Project_web/live/post_live/form_component.ex:12: ProjectWeb.PostLive.FormComponent.tags_input/1
    (Project 0.1.0) lib/Project_web/live/post_live/form_component.html.heex:21: anonymous fn/3 in ProjectWeb.PostLive.FormComponent.render/1
    (phoenix_live_view 0.16.4) lib/phoenix_live_view/diff.ex:356: Phoenix.LiveView.Diff.traverse/6
    (phoenix_live_view 0.16.4) lib/phoenix_live_view/diff.ex:444: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/6
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.16.4) lib/phoenix_live_view/diff.ex:356: Phoenix.LiveView.Diff.traverse/6
    (phoenix_live_view 0.16.4) lib/phoenix_live_view/diff.ex:444: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/6
    (elixir 1.12.2) lib/enum.ex:2385: Enum."-reduce/3-lists^foldl/2-0-"/3
    (phoenix_live_view 0.16.4) lib/phoenix_live_view/diff.ex:356: Phoenix.LiveView.Diff.traverse/6
    (phoenix_live_view 0.16.4) lib/phoenix_live_view/diff.ex:594: Phoenix.LiveView.Diff.render_component/9
    (phoenix_live_view 0.16.4) lib/phoenix_live_view/diff.ex:202: Phoenix.LiveView.Diff.write_component/4
    (phoenix_live_view 0.16.4) lib/phoenix_live_view/channel.ex:483: Phoenix.LiveView.Channel.component_handle_event/6
    (stdlib 3.15.2) gen_server.erl:695: :gen_server.try_dispatch/4
    (stdlib 3.15.2) gen_server.erl:771: :gen_server.handle_msg/6
    (stdlib 3.15.2) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: %Phoenix.Socket.Message{event: "event", join_ref: "6", payload: %{"cid" => 2, "event" => "validate", "type" => "form", "uploads" => %{}, "value" => "_method=put&_csrf_token=AhEZEDQJCT1fa1Q-LB8qUCkbKC46BgVmExKhU9Hv497rcLEdhMRmTIf1&post%5Bbody%5D=boddy&post%5Btitle%5D=titleeeeeeee&post%5Btags%5D=tag5%2C+tag6a&_target=_method"}, ref: "7", topic: "lv:phx-FrWGBciIPq3uvASC"}
State: %{components: {%{1 => {ProjectWeb.ModalComponent, :modal, %{__changed__: %{}, component: ProjectWeb.PostLive.FormComponent, flash: %{}, id: :modal, myself: %Phoenix.LiveComponent.CID{cid: 1}, opts: [id: 2, title: "Edit Post", action: :edit, post: %Project.Blog.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "boddy", id: 2, inserted_at: ~N[2021-11-08 05:28:01], tags: [%Project.Blog.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 3, inserted_at: ~N[2021-11-08 05:28:01], name: "tag5", updated_at: ~N[2021-11-08 05:28:01]}, %Project.Blog.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 4, inserted_at: ~N[2021-11-08 05:28:01], name: "tag6", updated_at: ~N[2021-11-08 05:28:01]}], title: "titleeeeeeee", updated_at: ~N[2021-11-08 05:28:01]}, return_to: "/posts/2"], return_to: "/posts/2"}, %{__changed__: %{}, root_view: ProjectWeb.PostLive.Show}, {238059331108390412792742767147817608882, %{}}}, 2 => {ProjectWeb.PostLive.FormComponent, 2, %{__changed__: %{}, action: :edit, changeset: #Ecto.Changeset<action: nil, changes: %{}, errors: [], data: #Project.Blog.Post<>, valid?: true>, flash: %{}, id: 2, myself: %Phoenix.LiveComponent.CID{cid: 2}, post: %Project.Blog.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "boddy", id: 2, inserted_at: ~N[2021-11-08 05:28:01], tags: [%Project.Blog.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 3, inserted_at: ~N[2021-11-08 05:28:01], name: "tag5", updated_at: ~N[2021-11-08 05:28:01]}, %Project.Blog.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 4, inserted_at: ~N[2021-11-08 05:28:01], name: "tag6", updated_at: ~N[2021-11-08 05:28:01]}], title: "titleeeeeeee", updated_at: ~N[2021-11-08 05:28:01]}, return_to: "/posts/2", title: "Edit Post"}, %{__changed__: %{}, root_view: ProjectWeb.PostLive.Show}, {154887885556391320998347709668667816266, %{1 => {47719165273871442880795992905670311081, %{1 => {216949147270072621760397562069327651832, %{}}, 2 => {339048473153300537925374445222079634425, %{}}, 3 => {241514271077641274477167451145713493581, %{}}}}}}}}, %{ProjectWeb.ModalComponent => %{modal: 1}, ProjectWeb.PostLive.FormComponent => %{2 => 2}}, 3}, join_ref: "6", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, flash: %{}, live_action: :edit, page_title: "Edit Post", post: %Project.Blog.Post{__meta__: #Ecto.Schema.Metadata<:loaded, "posts">, body: "boddy", id: 2, inserted_at: ~N[2021-11-08 05:28:01], tags: [%Project.Blog.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 3, inserted_at: ~N[2021-11-08 05:28:01], name: "tag5", updated_at: ~N[2021-11-08 05:28:01]}, %Project.Blog.Tag{__meta__: #Ecto.Schema.Metadata<:loaded, "tags">, id: 4, inserted_at: ~N[2021-11-08 05:28:01], name: "tag6", updated_at: ~N[2021-11-08 05:28:01]}], title: "titleeeeeeee", updated_at: ~N[2021-11-08 05:28:01]}}, endpoint: ProjectWeb.Endpoint, id: "phx-FrWGBciIPq3uvASC", parent_pid: nil, root_pid: #PID<0.854.0>, router: ProjectWeb.Router, transport_pid: #PID<0.846.0>, view: ProjectWeb.PostLive.Show, ...>, topic: "lv:phx-FrWGBciIPq3uvASC", upload_names: %{}, upload_pids: %{}}

Could you post your handle_event for the "validate" message?
It looks like the value in :tags after you handled the "validate" step is a string.

You could check by adding a IO.inspect between the input_value(:tags) and the Enum.map, e.g:

def tags_input(form) do
    tags_string =
      form
      |> input_value(:tags)
      |> IO.inspect()
      |> Enum.map(&tag_to_string/1)
      |> Enum.join(", ")

    text_input(form, :tags, value: tags_string)
  end
1 Like
Got unknown event: {"validate",
 %{
   "_csrf_token" => "c0Q3HzJZAgFTfwQlBQEMexkFNw47eRYS4-egSiCJ8-giJRcOXSMMU6uE",
   "_method" => "put",
   "_target" => ["post", "tags"],
   "post" => %{
     "body" => "boddy",
     "tags" => "tag5, tag6a",
     "title" => "titleeeeeeee"
   }
 }}

# form_component_ex
  def handle_event("validate", %{"post" => post_params}, socket) do
    changeset =
      socket.assigns.post
      |> Blog.change_post(post_params)
      |> Map.put(:action, :validate)

    {:noreply, assign(socket, :changeset, changeset)}
  end
# blog.ex
  def change_post(%Post{} = post, attrs \\ %{}) do
    post
    |> Repo.preload(:tags)
    |> Post.changeset(attrs)
  end
# post.ex
  def changeset(post, attrs) do
    post
    |> cast(attrs, [:body, :title])
    |> validate_required([:body, :title])
  end