---
title: "How Phoenix LiveView Form Auto-Recovery works"
---

# How Phoenix LiveView Form Auto-Recovery works

![Two birds holding a gun made out of feet.](https://fly.io/phoenix-files/how-phoenix-liveview-form-auto-recovery-works/assets/form-recovery-cover.webp)

[Annie Ruygt](https://annieruygtillustration.com/)

We’re Fly.io and we transmute containers into VMs, running them on our hardware around the world. We have fast booting VM’s, so why not [take advantage of them](https://fly.io/speedrun/)?

Phoenix LiveView is a server-side frontend framework and with this great power comes great responsibility on the developer. A very common source of frustration is around Forms, their assigns values and connection resets or deploys. In this post we’ll discuss how this all works and how it enables Form Auto-Recovery.

Let’s say we have this basic LiveView with a form and a `query` input:

```
defmodule FormExamples.FormsLive do
  use FormExamplesWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok,
      socket
      |> assign(form: to_form(%{"query" => nil}))
    }
  end

  def render(assigns) do
    ~H"""
    <.simple_form for={@form} phx-submit="save" id="form" class="mb-20">
      <.input field={@form[:query]} label="Search" />
    </.simple_form>
    """
  end

  def handle_event("save", %{"query" => q}, socket) do
    {:noreply,
      socket
      |> assign(form: to_form(%{"query" => nil}))
      |> put_flash(:info, "Form submitted with query: #{q}")}
  end
end
```

When we type into the text box and hit enter, our `save` callback is called and we reset the form. Before we move on, can you see the problem with this form?

On our Form submit we are `assign`ing `"query" => nil` but `"hello"` remains in our text box. What’s going on here?

Spoiler: We did not change the `assign` value while we were typing the query, in LiveView’s view of the world the `form`‘s `query` value is nil, and it never changed. These implications will be discussed further in the article.

## The Problem of State

LiveView and, any good frontend framework, help us organize our front end code around encapsulating state within a component. Meaning the only place we need to think about the state of a view is in our `assigns`, there will be no other references mutating our values outside of the component.

Unfortunately in all frameworks this a lie, or a helpful fiction. All frontend frameworks actually have to deal with three(!) separate local states and keep them in sync, for example here are the 3 states for LiveView:

- Server, where we keep track of assigns, session and the WebSocket related state.
- Browser HTML DOM state.
- Browser Internal DOM state.

In react the first state would be the Component/state/props, they still need to maintain the HTML and the internal DOM State. This is why they have so many functions around `use*` now to encapsulate the many different ways you might want to manage state and the dom.

In LiveView flow of state is as follows:

1. Developer changes `assigns`.
2. LiveView calculates the minimum HTML Diff changes and sends it down the WebSocket.
3. LiveView.js sends the changes to the virtual DOM library [`morphdom`](https://github.com/patrick-steele-idem/morphdom) which alters the HTML DOM.
4. User changes inputs, which *only* changes the Browser Internal DOM State.

Knowing this maybe we can guess what went wrong with our form, and it happens right at point 1, the `assigns` value for the `query` value never changed. No diff was calculated, the browser HTML never changed so the HTML Input `value` never changed, all the while the Browsers Internal DOM State value did change.

Here is how we can change our example to reset the value on submit:

```
defmodule FormExamples.FormsLive do
  use FormExamplesWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok,
      socket
      |> assign(form: to_form(%{"query" => nil}))
    }
  end

  def render(assigns) do
    ~H"""
    <.simple_form for={@form} phx-submit="save" phx-change="validate" id="form" class="mb-20">
      <.input field={@form[:query]} label="Search" />
    </.simple_form>
    """
  end

  def handle_event("validate", %{"query" => q}, socket) do
    {:noreply, assign(socket, form: to_form(%{"query" => q}))}
  end

  def handle_event("save", %{"query" => q}, socket) do
    {:noreply,
      socket
      |> assign(form: to_form(%{"query" => nil}))
      |> put_flash(:info, "Form submitted with query: #{q}")}
  end
end
```

Here we added a `phx-change` to our form, with `handle_event` function that changes the form and query `assigns` whenever the form changes. Now when we hit `submit` our input would reset to nil because every single keypress we informed LiveView of this change! We can follow the flow of state here:

1. User changes the text box, firing a `phx-change` event.
2. Handle event `assign` changes the LiveView state.
3. LiveView calculate s a diff that is essentially `set input.value to assigns.query`
4. LiveView.js and `morphdom` sets the HTML DOM value of our input.
5. Which is the same as the current HTML Internal Dom value so no change happens that the user can see.

## Enter A Server Restart

Now this is where the[Auto-Recovery](https://hexdocs.pm/phoenix_live_view/0.20.14/form-bindings.html#recovery-following-crashes-or-disconnects) Magic happens when we deploy or the [process](https://fly.io/phoenix-files/a-liveview-is-a-process/) restarts:

1. On re-connect a `phx-change` event is fired with the current DOM for every Form that has the same `id` as a reconnect mounts html.
2. Handle event `assign` changes the LiveView internal state with the validate params.
3. LiveView calculate s a diff that is essentially `set input.value to assigns.query`.
4. LiveView.js and `morphdom` sets the HTML DOM value of our text box.
5. Which is the same as the current HTML Internal Dom value so no change happens that the user can see.

The only difference here a little bit of Magic that LiveView does when it knows it’s reconnecting to an existing DOM State. If the current DOM has a Form with the same `id`, instead of replacing it, it fires the `change` event which should alter the LiveView’s current assigns with the previous state! And there you have it—fully recoverable forms with no server-side state at all! This works assuming the Form’s `id` remains unchanged.

And if you don’t believe me I pushed a live example [here](https://form-examples.fly.dev/) with code [here](https://github.com/jeregrine/form_examples/blob/main/lib/form_examples_web/live/forms_live.ex).

## Takeways

1. Always give each Form a unique and consistent `id`.
2. Always have a `phx-change` event for Forms that updates the server side `assign`.

By understanding its inner workings of form handling and auto-recovery mechanisms, we can unlock the full potential of LiveView to create seamless user experiences. I encourage you to check on your own forms and inputs and make sure they have `ids` and `phx-change` events wired up to give your users a better user experience!

## Fly.io ❤️ Elixir

Fly.io is a great way to run your Phoenix LiveView apps. It’s really easy to get started. You can be running in minutes.

[Deploy a Phoenix app today! →](https://docs.fly.io/elixir)
