Fundamentals
Your first mistakes
Read time: 6 minutes
#Preparing for worst-case scenarios
Mistakes happen! Sometimes we type something wrong, sometimes we forget part of the code we already thought about writing. Although frustrating, exceptions in the code are there to help you. In this guide we will learn how to handle some of these exceptions so that when you experience them in real life you already know what to do. I chose these errors and decided on them so early in this track because they are errors that LiveView beginners that I helped have experienced multiple times.
#I forgot to add an assign
!
Let's create a LiveView and forget to add an assign in mount/3
but we will use it in render/1
. Let's create a file called missing_assign.exs
and run it:
Mix.install([
{:liveview_playground, "~> 0.1.1"}
])
defmodule PageLive do
use LiveviewPlaygroundWeb, :live_view
def mount(_params, _session, socket) do
{:ok, socket}
end
def render(assigns) do
~H"""
Hello <%= @name %>
"""
end
end
LiveviewPlayground.start()
In your browser you should be seeing an "Internal Server Error" and in your terminal several error lines should appear:
08:32:27.589 [error] #PID<0.374.0> running LiveviewPlayground.Endpoint (connection #PID<0.372.0>, stream id 2) terminated
Server: localhost:4000 (http)
Request: GET /
** (exit) an exception was raised:
** (KeyError) key :name not found in: %{
socket: #Phoenix.LiveView.Socket<
id: "phx-F7_-oDyLb_kqfgAD",
endpoint: LiveviewPlayground.Endpoint,
view: PageLive,
parent_pid: nil,
root_pid: nil,
router: LiveviewPlayground.Router,
assigns: #Phoenix.LiveView.Socket.AssignsNotInSocket<>,
transport_pid: nil,
...
>,
__changed__: %{},
flash: %{},
live_action: :index
}
priv/examples/your-first-mistakes/missing_assign.exs:14: anonymous fn/2 in PageLive.render/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:398: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:544: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
(elixir 1.16.1) lib/enum.ex:2528: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:396: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:139: Phoenix.LiveView.Diff.render/3
(phoenix_live_view 0.18.18) lib/phoenix_live_view/static.ex:252: Phoenix.LiveView.Static.to_rendered_content_tag/4
(phoenix_live_view 0.18.18) lib/phoenix_live_view/static.ex:135: Phoenix.LiveView.Static.render/3
Let's digest this message step by step! The first useful information here is precisely the first error line:
08:32:27.589 [error] #PID<0.374.0> running LiveviewPlayground.Endpoint (connection #PID<0.372.0>, stream id 2) terminated
It indicates that the connection to your user's LiveView was suddenly terminated because the process containing the view died. In other words, it makes sense that the error is "Internal Server Error" because something was not handled by the programmer who created the LiveView. The next extremely important information is precisely the exception that caused your LiveView to die (that is, cause an exit
):
** (exit) an exception was raised:
** (KeyError) key :name not found in: %{
socket: #Phoenix.LiveView.Socket<
id: "phx-F7_-oDyLb_kqfgAD",
endpoint: LiveviewPlayground.Endpoint,
view: PageLive,
parent_pid: nil,
root_pid: nil,
router: LiveviewPlayground.Router,
assigns: #Phoenix.LiveView.Socket.AssignsNotInSocket<>,
transport_pid: nil,
...
>,
__changed__: %{},
flash: %{},
live_action: :index
}
In Elixir a KeyError
means that at a given moment you had a map and tried to access a key that does not exist in it such as my_map.key_that_does_not_exist
. Recall that in our render/1
we used @name
which is the same as assigns.name
it makes sense that it was a KeyError
. To make the above error message even clearer, we can simplify it as:
** (exit) an exception was raised:
** (KeyError) key :name not found in: %{
socket: %Phoenix.LiveView.Socket{},
__changed__: %{},
flash: %{},
live_action: :index
}
Remember when we talked about what a socket
has in the previous class? They have flash
, __changed__
and live_action
. Although the exception was not extremely obvious, we can interpret that this is the lack of an assign. But imagine that you have a giant LiveView project. How to find where this assign is missing? Let's look at the stack trace
.
priv/examples/your-first-mistakes/missing_assign.exs:14: anonymous fn/2 in PageLive.render/1
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:398: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:544: anonymous fn/4 in Phoenix.LiveView.Diff.traverse_dynamic/7
(elixir 1.16.1) lib/enum.ex:2528: Enum."-reduce/3-lists^foldl/2-0-"/3
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:396: Phoenix.LiveView.Diff.traverse/7
(phoenix_live_view 0.18.18) lib/phoenix_live_view/diff.ex:139: Phoenix.LiveView.Diff.render/3
(phoenix_live_view 0.18.18) lib/phoenix_live_view/static.ex:252: Phoenix.LiveView.Static.to_rendered_content_tag/4
(phoenix_live_view 0.18.18) lib/phoenix_live_view/static.ex:135: Phoenix.LiveView.Static.render/3
A stack trace
demonstrates the chain of function calls until reaching the exception in your code (call stack). Each line has the format (version_dependency_name) folder/file_name:line: ModuleName.function_name/arity
. Being able to read stack traces will make your day-to-day life as a programmer much simpler. Here's the first tip on how to find out where issue is: ignore all lines that are from libraries (those that start as parentheses). With that we are left with:
priv/examples/your-first-mistakes/missing_assign.exs:14: anonymous fn/2 in PageLive.render/1
Interpreting the trace we have left:
-
In the file
priv/examples/your-first-mistakes/missing_assign.exs
. -
On line
14
. -
We have an anonymous function that takes two arguments (
anonymous fn/2
). -
Running inside the PageLive.render function that takes one argument (
PageLive.render/1
).
What is on line 14 of this file? Hello <%= @name %>
. Diagnosis: assigns name
does not exist. Solution: add it to our mount/3
:
def mount(_params, _session, socket) do
socket = assign(socket, name: "Mundo")
{:ok, socket}
end
What was that anonymous fn/2
?
<%= %>
to interpolate code? Your @name
is inside the anonymous interpolation function. This is something internal to LiveView, the important thing was to know the file + line + function.
#Immutability
Let's create a new file called immutable.exs
like this:
Mix.install([
{:liveview_playground, "~> 0.1.1"}
])
defmodule PageLive do
use LiveviewPlaygroundWeb, :live_view
def mount(_params, _session, socket) do
assign(socket, name: "Immutable")
{:ok, socket}
end
def render(assigns) do
~H"""
Hello <%= @name %>
"""
end
end
LiveviewPlayground.start()
Can you identify the error? Run the file with elixir immutable.exs
and open your page. You will see the same KeyError
that we talked about earlier. This time we assigned name
, shouldn't it work?
To understand this problem we need to briefly talk about immutability. In Elixir you cannot modify variables. See the following example:
person = %{name: "Lubien"}
Map.put(person, :name, "João")
dbg(person) # Still %{name: "Lubien"}
This happens because, unlike programming languages with mutable values (such as JavaScript), data in Elixir is immutable. You cannot modify an existing map but you can create a new map with something modified.
person = %{name: "Lubien"}
person = Map.put(person, :name, "João")
dbg(person) # %{name: "João"}
In this case we create a second map and assign its value to the person
identifier. If you modify data you will probably want to store the modification in the original or in another variable. Returning to our LiveView, the code with the problem is right here:
assign(socket, name: "Immutable")
{:ok, socket}
The solution is quite simple. Just as Map.put returns a new map with the new data, the assign/2
function returns a new socket with the added assign:
socket = assign(socket, name: "Immutable")
{:ok, socket}
#In short
-
If you see a
KeyError
saying that it was not possible to access a property of a map that haslive_action
,socket
andflash
, suspect that you forgot to assign it. - Remember that Elixir is an immutable programming language so you need to store the result of function calls somewhere.
Feedback
Got any feedback about this page? Let us know!