Liveview input forms are not controlled by the server state when input is in focus

In react, there is the concept of a “controlled component”. Typically, input elements and the like manage their own state and respond to user input. However, sometimes it is convenient to let react store the state of the input, so it can serve as the single source of truth.

I’ve been experiencing difficulties with liveview where it seems like the input is not controlled by the server state, despite the state changing.

Contrived example:

def mount(_params, _session, socket) do
    {:ok,
     assign(socket, %{
       value: ""
     })}
  end

  def handle_event("set_value", %{"key" => "ArrowUp"}, socket) do
    rand = :rand.uniform(1_000_000)
    {:noreply, assign(socket, value: rand)}
  end

  def handle_event("set_value", _params, socket) do
    {:noreply, socket}
  end
<div phx-window-keyup="set_value">
  <input type="text" value={@value} />
</div>

The value of the input should be set the match the value of the state of the liveview. Anytime an ArrowUp event occurs, it should select a random number, and set the value of the input to that number. This works fine when the input is not in focus, and the value in that input element is updated accordingly.

The confusing part occurs when you click into the input such that it is in a focused state. Now, if you try to press up on the arrow pad, the handle_event call does fire and does update the state, but the input value does not follow along. You can type whatever you want – in react it would not let you type anything, since the react state is the single source of truth and it would keep insisting that the value of the input be the random number.

1 Like

Hi @mcgingras – this behaviour is documented in the Javascript client specifics section of the LiveView Form bindings guide:

The JavaScript client is always the source of truth for current input values. For any given input with focus, LiveView will never overwrite the input’s current value, even if it deviates from the server’s rendered updates.

1 Like

@mcrumm wow, thanks so much for sending this along. This certainly seems to confirm what I thought might be happening. Do you know if there is anyway around this behavior?

I’m running into an issue where I am submitting a form on enter press but do not want the input to clear. Since the input has focus while the user presses enter on the keyboard, it clears the form. I thought keeping the value in the app state and pushing that along to the UI would allow it to stay, but due to the above, it does not.

Thanks for pointing me in the right direction!

I managed to resolve the issue by using AlpineJS to manage the client state. I passed the LV state to Alpine so its duplicated there. Changes to the LV app state propagate to Alpine, and since Alpine works at the level of the Javascript client it is now working as expecting.

I don’t love the idea of having to duplicate my state in both LV and Alpine… but, its working. :slight_smile:

Thanks

1 Like