While learning, I came across this weird situation that got me scratching my head. Maybe it’s an Elixir thing that I’m doing right? Maybe I missed something obvious? Here’s the scenario…
I am trying to set the value of current_cat_id that lives in a LiveView socket with the following conditions:-
cat_edit = “false”
set_id = 2
To test the problem, I assign 99 to current_cat_id. (ie. current_cat_id = 99)
Since cat_edit is false, it then assigns 0 to the same variable. (ie. current_cat_id = 0)
But when it reaches the next line when set_id is greater than 0 , current_cat_id reverts back to 99 for some reason.
The IO.inspect output is
99
0
99
What caused it to revert back to 99?
def handle_event("add-cat", %{"text" => value}, socket) do
set_id = socket.assigns.current_set_id
socket = assign(socket, :current_cat_id, 99)
IO.inspect socket.assigns.current_cat_id
if (socket.assigns.cat_edit == "false") do
socket = assign(socket, :current_cat_id, 0)
IO.inspect socket.assigns.current_cat_id
end
cond do
set_id > 0 ->
IO.inspect socket.assigns.current_cat_id
true ->
IO.inspect "huh?"
end
{:noreply, socket}
end
Hey @carter, it isn’t that it reverted back to 99, it’s that you can’t assign inside of if at all, it’s a different lexical scope. Think of if more like an expression and less like a statement, and bind a value based on the return of that expression:
socket = if (socket.assigns.cat_edit == "false") do
assign(socket, :current_cat_id, 0)
else
socket
end
This is a common sticking point with immutable functional languages, keep at it!
Remembering that everything is an expression and returns a value is helpful. (That value may be nil, but nil is not nothing, it is a value!) Maybe think about it as transformations you want to apply to your data instead of “changing variables”.
It wouldn’t be uncommon to write
def maybe_reset_cat_id(socket, "false") do
# reset id to 0 when given "false", assign returns a new socket with
# the updated value.
assign(socket, :current_cat_id, 0)
end
def maybe_reset_cat_id(socket, _) do
# take no action for any other edit value, just return the socket
# we were given.
socket
end
def handle_event("add-cat", %{"text" => value}, socket) do
%{cat_edit: edit?} = socket.assigns
socket =
socket
# you could think of this as a series of transformations
# you are applying to the socket
|> maybe_reset_cat_id(edit?)
# remember each function returns a (maybe updated) socket
# to pass to the next function/transformation.
|> ... other stuff
# finally we rebind the name "socket" to the value returned
# by all our functions (i.e the `socket = ...`)
{:noreply, socket}
end
As I said, it’s super common to trip over this particular edge but in the end it’s actually very nice. The “always returns a value” property is really useful and the immutability makes code very easy to reason about, once you internalise its behaviour.