I am new to elixir and Pheonix
My issue is that i have a checkbox that has phx-change which calls a function change_todo which in turn makes a database change to a completed boolean. I am currently getting the result i want which is to toggle the completed value when the checkbox is clicked and i can log it and see the correct value but when i pass this value to my socket and update the for loop for my todos im not seeing the todos update and i dont know why. Its a pretty simple todo list.
This is my event handler
def handle_event("complete", %{"todo_id" => todo_id}, socket) do
todo_id = todo_id
case Todos.change_todo(todo_id) do
{:ok, completed} ->
updated_todos = Enum.map(socket.assigns[:todos], fn todo ->
if todo.id == todo_id do
%{todo | completed: completed}
else
todo
end
end)
# here i am correctly retrieving the UPDATED completed boolean for the clicked in checkbox
Logger.info(completed)
socket = assign(socket, todos: updated_todos)
# i believe here i can just assign that value to the SOCKET and somehow update the completed Variable
# in my UI
{:noreply, socket}
end
end
Here is the template portion which handles my checkbox
For checkboxes, the contents of the value property do not appear in the user interface. The value property only has meaning when submitting a form. If a checkbox is in checked state when the form is submitted, the name of the checkbox is sent along with the value of the value property (if the checkbox is not checked, no information is sent).
I don’t think that’s my issue here because when i refresh the page after checking a box my “line-through” class is applied and the box is checked. I am just wondering why LiveView is not re-rendering the component instead of me having to refresh the page
I guess you still need to do that and in worst case also some extra JavaScript stuff for value attribute change …
The JavaScript client is always the source of truth for current input values. For any given input with focus, LiveView will never overwrite the input’s current value, even if it deviates from the server’s rendered updates. This works well for updates where major side effects are not expected, such as form validation errors, or additive UX around the user’s input values as they fill out a form.
I’m pretty sure it’s the value={todo.completed} that is messing it up. field handled both name and value. Try removing it—LiveView certainly handles this as I have a very checkbox heavy app I’m currently working on and doing nothing special to make them happen
That’s not it either. I pretty much confirmed that im not updating my socket correctly
when i Logger.info both
updated_todos
and
socket
both of them still hold the todo with the wrong completed value
but if i log completed it is holding the correct todo with the correct completed value.
seems like that updated_todos map isn’t doing anything
Ok so after looking around online i feel dumb…i didnt need to loop over the todos as i was already updating the completed value in another function but i was on the right track with assigning the todo back to the socket. My issues was i just needed to retrieve all the todos again and assign that to the socket. example:
Its updating correctly now but i get them in a new order each time…so now im wondering if there is a order_by function for ecto? Anyway thanks for the help.
You shouldn’t need to do this, there’s gotta be something else wrong.
For some unsolicited feedback, it’s more idiomatic (and flexible) to return the struct, even if just updating one field. So instead of {:ok, <bool>}, return {:ok, %Todo{}}. Then you can splice it in:
{:ok, %{id: updated_id} = updated_todo} ->
socket =
update(socket, :todos, fn todos ->
Enum.map(todos, fn ->
%{id: ^updated_id} -> updated_todo
todo -> todo
end)
end)
{:noreply, socket}
(there are various ways to write that, that’s just how I do it)