How Phoenix LiveView handles State

How Phoenix LiveView handles State

LiveView is an innovative library created for Phoenix which gives us the infrastructure we need to develop interactive, real-time, distributed web apps quickly and easily. LiveView powered applications are stateful on the server with bidirectional communication via WebSockets, offering a vastly simplified programming model compared to JavaScript alternatives, but how state is handled in this powerful library? Let's check that out.

LiveView manages the state of your page in a long-lived process that loops through a set of steps again and again. Your application receives events, changes the state, and then renders the state, over and over. This is the LiveView flow.

When we build live views, we focus on managing and rendering our view’s state, called a socket. We can do that by assigning an initial value in the mount/3 function, and by updating that value using several handler functions. Those functions can handle input from processes elsewhere in our application, as well as manage events triggered by the user on the page, such as mouse clicks or keystroke presses. After a handler function is invoked, LiveView renders the changed state with the render/1 function.

The Phoenix.LiveView.Socket is the module responsible for creating the socket struct, whenever you see one of these sockets struct as a variable or an argument within a live view, you should immediately recognize it as the data that constitute the live view's state. Let's take a closer look into the socket struct

iex(1)> Phoenix.LiveView.Socket.__struct__ #Phoenix.LiveView.Socket<
  assigns: %{},
  changed: %{},
  endpoint: nil,
  id: nil,
  parent_pid: nil,
  root_pid: nil,
  router: nil,
  view: nil,
... >

> This is the basic structure of a socket struct, now you can start to get an idea of how socket structs represent live view state. The socket struct has all of the data that Phoenix needs to manage a LiveView connection.

The data inside of a socket struct is mostly private. The most important key and the one you'll interact the most is assigns: %{}. That's where you are going to keep all the data related to your app describing its state. Every running live view keeps data describing state in a socket. You’ll establish and update that state by interacting with the socket struct’s :assigns key.

When Phoenix processes a LiveView request, two things happen. First, Phoenix processes a plain HTTP request. The router invokes the LiveView module, and that calls the mount/3 function and then render/1. This first pass renders a static, SEO-friendly page that includes some JavaScript. That page then opens a persistent connection between the client and the server using WebSockets. After Phoenix opens the WebSocket connection, our LiveView program will call mount/3 and render/1 again. At this point, the LiveView lifecycle starts up the LiveView loop. The live view can now receive events, change the state, and render the state again. This loop repeats whenever live view receives a new event. This flow represents a common pattern in Elixir and Phoenix programming that you’ll see again and again as you work in LiveView. When the LiveView process starts up, the socket is initialized or constructed. The mount/3 function further reduces over that socket to make any state changes. Then, the render/1 function converts that socket state into markup which is delivered to the client in the browser.

In conclusion, handling state with LiveView is pretty easy because we don’t have to worry about how events get sent to a live view or how markup is re-rendered when state changes. We just need to implement the mount/render workflow which sets the initial state of a live view then think about how to implement your own event handler functions, and teach them how to change state. LiveView does all the hard work of detecting events, such as form submits or clicks on a link, and invokes those handlers for us. That's basically how you can manage state in LiveView, just initialize it and teach it how to change its value, the rest is up to the framework's magic.