Trying to set the value of current_cat_id that lives in a LiveView socket with conditions - am I missing something?

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
5 Likes

Thank you. Learning something new about Elixir everyday…

1 Like

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.

4 Likes