Live view receiving a message wipes inputs

I have a live view, which, among other things, repeatedly reads a timer running on the server and updates the page with the timer’s value. The same view also has a one-field form. The form is unrelated to the timer, and the timer display markup is not part of the form. I noticed that once the timer reads start, whatever a user tries to type in the text input field of the form is deleted when the timer updates. Because I am reading the timer very frequently, it is becomes impossible to submit a value because anything entered in the field is wiped before a submit button can be pressed.

If I disable the timer update, everything is fine.

I wonder if somehow the form part of the page is getting included in a diff even though only the timer part is changing, and I just misunderstand how Live View selectively updates parts of the page, or if the way that I am broadcasting changes to several users is the problem, (or something dumber). As such I’ve included the relevant code for both aspects of the update cycle below. Thank you everyone in advance for any help you can provide!


First, when the view is mounted it subscribes to a PubSub topic in order to push changes to multiple users (it’s an auction application, so everyone needs to see when someone submits a bid, for example).

  def mount(session, socket) do
    if connected?(socket) do
      Phoenix.PubSub.subscribe(AuctionRoom.PubSub, "room:1")
    end

    {:ok, assign(socket, ...., timer: "Not Started")}
 end

When an auction is started, the timer starts on the server, and we read it every 100 milliseconds:

  def handle_event("new_auction", _value, socket) do
    RoomServer.new_auction(socket.assigns.room_pid) # -> starts timer on the server
    if connected?(socket) do
      :timer.send_interval(100, self(), :read_timer)
    end
    broadcast_socket(socket, [status: "started"])
  end

  def handle_info(:read_timer, socket) do
    timer = RoomServer.read_timer(socket.assigns.room_pid)
    broadcast_socket(socket, [timer: timer])
  end

broadcast_socket/2 publishes a :room_update event to the PubSub topic subscribers, and that event handler updates the view.

  defp broadcast_socket(socket, assigns) do
    Phoenix.PubSub.broadcast!(AuctionRoom.PubSub, "room:1", {:room_update, assigns})
    {:noreply, socket}
  end

  def handle_info({:room_update, assigns}, socket) do
    {:noreply, assign(socket, assigns)}
  end

The applicable part of the template looks like so:

<div id="timer_container">Time: <%= @timer %></div>

<form phx-submit="bid_custom">
  <input id="bid_amount" name="bid_amount" type="number">
  <button id="submit_custom" type="submit">Bid</button>
</form>

I haven’t tried this, but I suspect the user_live example will give you some pointers, particularly https://github.com/chrismccord/phoenix_live_view_example/blob/master/lib/demo_web/live/user_live/new.ex

Basically, the socket carries a changeset with all the edits to date. The form has a phx_change handler (see https://github.com/chrismccord/phoenix_live_view_example/blob/master/lib/demo_web/templates/user/form.html.leex for the template with the phx_change wireup and new.ex for the “validate” handler. Any change in the form calls the validate handler and that updates the changeset. The form then re-renders the template. The form submit (handled by “save”) applies the changeset to the repo.

If you don’t want an Ecto Repo, you could use an embedded schema to carry the data and perform validation etc, then use it however you want when the user submits.

Thinking about it some more - probably the easiest way to handle it is to separate the fast-updating part into a separate liveview altogether.

Thanks for the suggestion! I think separating it to a different view is a good idea to try.

I wish I understood why this is even an issue since typically Live View is only updating parts of the page that change, and the form isn’t tied to the timer at all.

As for the first note - I’m not using a DB and I’m literally just sending one value that is only going to have meaning for a few seconds, so I would rather avoid adding Ecto just for that. I don’t think that it would make a difference as the form works just fine if I turn off the timer piece.

I appreciate the examples - the code above builds on them for the most part. I intentionally don’t fire anything on phx-change. For now it’s sufficient to wait to give feedback on the data’s validity until after it is submitted, because the main check is whether the value is higher than the current bid, and with bids firing left and right, you could type a number and it would be valid, and then by the time you click submit a second later, another bid has arrived and yours is not valid anymore.

I would think that LiveView only updating part of the template is an optimisation rather than a guarantee - if you want a guarantee you would need to assign the data to be rendered to the socket (or have two LiveViews). The earliest demos of LiveView did re-render the whole template each time and send it down the wire IIRC.

Moving the timer to a separate view addressed the issue (I am a hobbyist so it took me this long to have the spare time to move it incorrectly three different ways before finally getting it right).

Thanks for your help - it seems like a small thing but this was the last issue stopping me from having my application pretty much work so it meant a lot to me to get past it.

1 Like