I want to write a quick post about this paragraph in the LiveView docs. It says
A LiveView is just a process that receives events as messages and updates its state. The state itself is nothing more than functional and immutable Elixir data structures. The events are either internal application messages...or sent by the client/browser.
I didn't really get what this meant at first. It's not that this paragraph is worded poorly or anything, I'm just relatively new to Phoenix and LiveView and I had to work through an example on my own to understand it fully. I want to share that example here.
So first, a bit about Elixir processes.
Elixir apps contain one or more processes. Processes can spawn other processes. For example, if you open up an iex
shell, you can spawn a new process that runs this greet function
defmodule Greet do
def greet(name) do
IO.puts("Hello, #{name}!")
end
end
with spawn(Greet, :greet, ["friend"])
. This will print Hello, friend!
to the console and will return the pid of the newly spawned process.
You can also send messages to processes with the send/2
function, and those processes can receive messages with receive
. For example, we could change our module to
defmodule Greet do
def greet(name) do
receive do
:print -> IO.puts("Hello, #{name}!")
end
end
end
Now if we spawn a new process that runs the greet
function with pid = spawn(Greet, :greet, ["friend"])
, we won't immediately see the message printed to the console. We can tell that process to print the message with send(pid, :print)
.
You can also register processes with a name so you don't have to always refer to them by their pid. For example, we could run
pid = spawn(Greet, :greet, ["friend"])
Process.register(pid, :greeter)
send(:greeter, :print)
Registering the pid with the name :greeter
lets us send the message using that name instead of the pid.
So, let's bring this back to LiveView.
As that paragraph in the documentation says, "A LiveView is just a process". As such, we can send it messages, just like we could with any other process.
The cool thing about LiveView processes is that they maintain their own state, and whenever something updates that state, the LiveView process re-renders its html template and pushes the changes to the browser.
Say we have a LiveView module that looks like this (this is on version 0.18.16, btw)
defmodule MyAppWeb.IndexLive do
use MyAppWeb, :live_view
attr :name, :string, default: "stranger"
def render(assigns) do
~H"""
<div>
<p>Hello, <%= @name %>!</p>
</div>
"""
end
def mount(_params, _session, socket) do
Process.register(self(), :index)
{:ok, socket}
end
def handle_info({:update_name, name}, socket) do
socket =
socket
|> assign(:name, name)
{:noreply, socket}
end
end
When you visit this LiveView in your browser, mount/3
is called, where we register the new LiveView process with the name :index
. (If you didn't know, self()
in Elixir refers to the pid of the process that that line is being executed by, which is, in this case, the LiveView process)
So when you visit this LiveView in your browser, you'll see the message "Hello, stranger!", and the LiveView handling your browsing session is registered under the name :index
.
Now, a LiveView maintains its state in a variable it calls socket
, and it responds to messages from other processes in the handle_info
function (as opposed to the receive
block in our earlier example). So, whenever we send a message to the LiveView process, that process calls handle_info
, which updates its state by setting the :name
property.
If you run your application with iex -S mix phx.server
, then you'll be able to go into the iex
console and send a message to the LiveView process. The only message that our LiveView process handles is one of the format {:update_name, name}
.
So if we say send(:index, {:update_name, "partner"})
in the console, the LiveView process will handle that message in handle_info
, which updates the process's state, which then updates the UI in the browser to read "Hello, partner!". Pretty cool!
Going through this example helped me to grasp the fact that a LiveView is just a process that maintains your UI state. When you update that LiveView process's state, it re-renders the html and sends the diff to the browser.
Top comments (2)
Thanks for this. I'm also pretty new to Elixir/Phoenix and was interested in the process boundaries for LiveViews, so I can avoid silly mistakes in my code structure. The docs are very detailed, but sometimes it feels like they flood you with info you probably don't need (as a newbie) and miss/glaze over the simple truths. This helped the penny drop for a couple of things in my mind. :)
I totally agree about their documentation. Super detailed, but I also miss simple things all the time.