A Phoenix LiveView is Just a Process

zachtylr21

Zach Taylor

Posted on March 17, 2023

A Phoenix LiveView is Just a Process

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
zachtylr21
Zach Taylor

Posted on March 17, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related