LiveView form field update life cycle

Hi Folks,

I’m writing a simple LiveView form with plain Map (no Ecto changesets here).

The idea is quite simple: when we first load, pick a default channel, and, on every submission, keep the last channel but clear the message (yes, this is yet-another-chat example).

The first time it submits, it works properly - but then - the following submissions don’t.

The whole file:

defmodule SimpleLiveChat do
  use AppWeb, :live_view

  def mount(_params, _session, socket) do
    empty = to_form(%{"channel" => "lobby"}, as: "chat")
    {:ok, assign(socket, form: empty)}
  end

  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash}>
      <.form id="message-form" for={@form} phx-submit="send">
        <.input field={@form[:message]} label="Message" />
        <.input field={@form[:channel]} label="Channel" />
        <.button variant="primary"> Send </.button>
      </.form>
    </Layouts.app>
    """
  end

  def handle_event("send", %{"chat" => params}, socket) do
    channel = params["channel"]
    f = to_form(%{"channel" => channel, "message" => nil}, as: "chat")

    {
      :noreply,
      socket
      |> assign(:form, f)
      |> put_flash(:info, "Message sent to #{channel}")
    }
  end
end

I tried this on 1.8.x and 1.7.x and the behavior is the same. What I’m missing here?

Thanks

One temporary solution I can apply: push a custom event event => clean the field on the client side (vanilla JS).

That is working, but it’s not the solution I really wanted.

// assets/js/app.js

window.addEventListener("phx:clear-me", (e) => {
  console.log(`cleaning`, e.detail.id);
  document.getElementById(e.detail.id).value = "";
});

and:

  def handle_event("send", %{"chat" => params}, socket) do
    channel = params["channel"]
    f = to_form(%{"channel" => channel, "message" => nil}, as: "chat")
    {
      :noreply,
      socket
      |> assign(:form, f)
      |> push_event("clear-me", %{"id" => "chat_message"})
      |> put_flash(:info, "Message sent to #{channel}")
    }
  end

I found the fix… I need to set temporary_assigns when mounting the view.

LiveView.html#mount/3

So, updated mount looks like this, and voilà, it’s working!

  def mount(_params, _session, socket) do
    empty = to_form(%{"channel" => "lobby"}, as: "chat")
    {:ok, assign(socket, form: empty), temporary_assigns: [form: nil]}
  end

Hi @John217Ross , thanks for taking the time to reply.

I tried both ways - reseting to an empty string "" and even nil, and neither of these approaches worked without the temporary_assigns extra argument.

Maybe I’m still missing something.

That’s a spambot, hence the nonsense answer.

The problem is likely that the server cannot override the client input’s value while the input is selected, which is a design choice LiveView makes to avoid race conditions due to server latency while typing.

This has come up many times before on here if you do a search, but I don’t think there is a “blessed” solution yet. One thing you can do is change the id of the input which will cause it to be reset. It is also possible for forms to have a reset button (which still sends an event to the server), so there is probably a way to coerce that into doing what you want. Both of those solutions are hacky, though.

Another solution is to use a JS hook, but I see you’re aware of that. I’m not actually sure why the temporary_assigns trick works.