Phx-vale gives (Ecto.Query.CastError)

I’m newbie to both Elixir and Phoenix, I’m creating a simple todo list app using phoenix live view,
here is my template

<form action="#" phx-submit="add">
  <%= text_input :todo, :title, placeholder: "What do you want to get done?" %>
  <%= submit "Add", phx_disable_with: "Adding..." %>
</form>

<%= for todo <- @todos do %>
<div>
<%= checkbox(:todo, :done,
  phx_click: "toggle_done",
   phx_value: todo.id ,
    value: todo.done) %>

  <%= todo.title %>
  <br>
  <%= submit "Delete", phx_disable_with: "Deleting...", phx_click: "Delete", phx_value: todo.id %>
  <hr>
</div>

<% end %>

and here is my handler function

 def handle_event("toggle_done", id, socket) do
    todo = Todos.get_todo!(id)
    Todos.update_todo(todo, %{done: !todo.done})
    {:noreply, fetch(socket)}
  end

the thing is that phx-value does return the id value form the database instead I get that error in my server

[error] GenServer #PID<0.2496.0> terminating
** (Ecto.Query.CastError) deps/ecto/lib/ecto/repo/queryable.ex:397: value `%{"altKey" => false, "ctrlKey" => false, "metaKey" => false, "pageX" => 81, "pageY" => 548, "screenX" => 871, "screenY" => 719, "shiftKey" => false, "value" => "", "x" => 81, "y" => 485}` in `where` cannot be cast to type :id in query:

from t0 in LiveViewTodos.Todos.Todo,
  where: t0.id == ^%{"altKey" => false, "ctrlKey" => false, "metaKey" => false, "pageX" => 81, "pageY" => 548, "screenX" => 871, "screenY" => 719, "shiftKey" => false, "value" => "", "x" => 81, "y" => 485},
  select: t0

    (elixir) lib/enum.ex:1948: Enum."-reduce/3-lists^foldl/2-0-"/3
    (elixir) lib/enum.ex:1440: Enum."-map_reduce/3-lists^mapfoldl/2-0-"/3
    (elixir) lib/enum.ex:1948: Enum."-reduce/3-lists^foldl/2-0-"/3
    (ecto) lib/ecto/repo/queryable.ex:161: Ecto.Repo.Queryable.execute/4
    (ecto) lib/ecto/repo/queryable.ex:17: Ecto.Repo.Queryable.all/3
    (ecto) lib/ecto/repo/queryable.ex:105: Ecto.Repo.Queryable.one!/3
    (live_view_todos) lib/live_view_todos_web/live/todo_live.ex:28: LiveViewTodosWeb.TodoLive.handle_event/3
    (phoenix_live_view) lib/phoenix_live_view/channel.ex:85: Phoenix.LiveView.Channel.handle_info/2
    (stdlib) gen_server.erl:637: :gen_server.try_dispatch/4
    (stdlib) gen_server.erl:711: :gen_server.handle_msg/6
    (stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Last message: %Phoenix.Socket.Message{event: "event", join_ref: "5", payload: %{"event" => "Delete", "type" => "click", "value" => %{"altKey" => false, "ctrlKey" => false, "metaKey" => false, "pageX" => 81, "pageY" => 548, "screenX" => 871, "screenY" => 719, "shiftKey" => false, "value" => "", "x" => 81, "y" => 485}}, ref: "7", topic: "lv:phx--6gLgtNd"}
State: %{components: {%{}, %{}, 0}, join_ref: "5", router: nil, serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{todos: [%LiveViewTodos.Todos.Todo{__meta__: #Ecto.Schema.Metadata<:loaded, "todo">, done: false, id: 4, ...}, %LiveViewTodos.Todos.Todo{__meta__: #Ecto.Schema.Metadata<:loaded, "todo">, done: false, ...}]}, changed: %{}, endpoint: LiveViewTodosWeb.Endpoint, id: "phx--6gLgtNd", parent_pid: nil, view: LiveViewTodosWeb.TodoLive, ...>, topic: "lv:phx--6gLgtNd", transport_pid: #PID<0.2486.0>, uri: nil}
[debug] QUERY OK source="todo" db=0.0ms
SELECT t0."id", t0."done", t0."title", t0."inserted_at", t0."updated_at" FROM "todo" AS t0 []`

Thank you for considering my question.

Hi @aligredo,

Welcome to the forum!

The structure of events changed slightly in a recent release of liveview. You now get all the metadata packaged into a map in the second argument of handle_event rather than just the phx-value that you set when building the checkbox. Take a read of this section in the liveview docs : https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#module-click-events

In summary, you need to change the phx-value tag of your checkbox to be something like phx-value-id, then match on the map being passed into your handle_event to extract the id, along the lines of:

def handle_event("toggle_done", %{"id" => id}, socket) do 
    todo = Todos.get_todo!(id)
    Todos.update_todo(todo, %{done: !todo.done})
    {:noreply, fetch(socket)}
  end
1 Like

Thank you so much, @mindok