HTML <input value doesn't reflect update in assign

I’m running into an issue with LiveView where the “value” setting in my html <input is not reflecting the change in my assigns (Code below).

AI is telling me that I need to write a hook to fix this and that seems odd to me. Is that true? Do I really have to write a hook to get HTML input-value to reflect an update in my LV assign?

I have tried a LOT to make this work so I want to list everything I’ve done.

  1. Updated all of my apps and Elixir (I’m not won LV 1.0.9
  2. Installed Liveview Debugger which is BRILLIANT! (thank you to whoever developed that!)
  3. Traced all changes and realized I had race conditions with my handle_events
  4. Fixed the handle_events so that there weren’t multiple updates happening

Process in a nutshell:

  • LiveView contains a SearchSelectId LV component which in turns contains a SearchSelect LV component
  • SearchSelect is your typical input field that allows the user to enter text, see a dropdown of existing database values, key down and select
  • SearchSelect messages SearchSelectId with input/selections
  • SearchSelectId processes the data further (checking for duplicates, etc) and messages LV with updated values
  • LV updates the data in a struct called SearchData and that updated data is then reflected in SearchSelect
  • LV is source of truth
  • updated values in LV components are reflected based on “update” calls from LV

Problem: HTML Input value not reflecting @search_input

  • from LV Debugger, I can see the SearchSelect LV component “update” function reflects the updated value of empty string
  • I can also see that it is registering the “change”
  • the IO.puts in my render reflects the new value for @search_input
  • and the div with the @search_input value ALSO reflects the new value for @search_input
  • the only thing not reflecting the updated @search_input is the <input value={@search_input}

AI (GPT-4o via you.com) is saying that my only fix is as follows:

When you set the value attribute in your render function, LiveView sends the updated HTML to the browser. However, if the user has interacted with the input field (e.g., typed something or selected a dropdown item), the browser prioritizes the current DOM state of the input field over the value attribute sent by LiveView. This is standard browser behavior.

In your case:

  1. The user selects “Fur” from the dropdown, which updates the input field’s DOM state to “Fur”.
  2. LiveView updates @search_input to an empty string and re-renders the component.
  3. The browser ignores the value attribute in the updated HTML because the user has already interacted with the input field.

How to Force the Input Value to Update

To ensure the input field reflects the value of @search_input, you need to explicitly reset the DOM state of the input field. This can be done using a JavaScript hook.

Solution: Use a LiveView Hook

Add a phx-hook to your input field to force the DOM state to match the value attribute whenever the component updates.

<input
id={"#{@id}-search-input"}
type="text"
name="search_input"
value={@search_input}
placeholder={@search_placeholder}
phx-target={@myself}
phx-hook="ForceValueUpdate"
class="w-full h-6 md:h-7 px-2 border border-gray-400 bg-white rounded-lg font-menu text-xs md:text-sm focus:outline-none focus:ring-0.5 focus:border-gray-700"
/>
let Hooks = {}
Hooks.ForceValueUpdate = {
updated() {
// Force the input's DOM value to match the value attribute
this.el.value = this.el.getAttribute("value")
}
}

// Add the hook to your LiveSocket initialization
let liveSocket = new LiveSocket("/live", Socket, {
hooks: Hooks,
params: {_csrf_token: csrfToken}
})

liveSocket.connect()

MY CODE (the relevant part):

<div class="w-full" >
      <.form
        :let={_f}
        for={%{}}
        as={:search_list}
        id={"#{@id}-form"}
        phx-target={@myself}
        phx-change="search"
        phx-submit="submit"
        phx-focusout="blur-search" >

        <div class="relative w-full h-auto flex flex-row flex-nowrap justify-between">

          <% IO.puts "SEARCH INPUT: #{inspect @search_input}" %>
          <!-- Input Field -->
          <div id={"#{@id}-type-input-#{@search_input}"} class="w-full relative">
            <input
              id={"#{@id}-search-input"}
              type="text"
              name="search_input"
              value={@search_input}
              placeholder={@search_placeholder}
              phx-target={@myself}

              class="w-full h-6 md:h-7 px-2 border border-gray-400 bg-white rounded-lg font-menu text-xs md:text-sm focus:outline-none focus:ring-0.5 focus:border-gray-700"
            />

1 Like

Yeah, this is weird LiveView’s behaviour and I think there should be an option to change that. Please see this topic for more information:

1 Like

Bummer that this can’t really be fixed with liveview. Trying to get around the issue using phx-keydown and other phx- events is how I ended up with race conditions so I’m not going down that path again. Sounds like I’ll need to implement that hook. Just curious … what did you end up doing?

I just wanted to give it a try and build some simple component, but when I realised I need JS for such a simple thing I just gave up. :frowning:

I guess it’s possible to write some generic hook, but I was not so much interested to continue working on it.

The alternative is weirder. You need to keep in mind that unlike a javascript framework, Liveview is asynchronous to the client javascript rendering thread. If your user start typing:

  • the typed text is sent to the server side for live validation
  • the server side will send it back through assign
  • your user has typed more during this round trip.

Do you really want to update input with the already outdated text from the assign?

It’s not a simple thing though - that’s the problem. A server and client concurrently messing with one an the same value is subject to all the nice complexities of a distributed state. There’s footguns everywhere in that.

@derek-zhou and @LostKobrakai What is the best solution to this problem? Am I doing something wrong by having the livview as the source of truth here because the updates are happening asynchronously? Is writing a hook a good idea or bad idea? I honestly don’t know javascript. So I’ll have to rely on that AI code. But I’m trying to figure out if that is the correct solution to this problem … or if I have done something wrong.

Just to clarify: the form has a phx-change on it so every time the user enters a letter, I am updating the liveview component and processing the input. So there is a round trip to the server. But I only message the LV when the user hits Enter or moves off the field.

You alone can answer that question. I would rethink my U/I so I don’t have this problem in the first place.

Here is an idea to avoid hook: use a new id, generated on the fly, for the input element each time you change the assigns. Then LV will remove the old input, add the new input, with the new value from the assigns.

1 Like

@annad Please don’t rely on chatbots. They are not AIs - it’s fake naming. They don’t think nor validate what they return.

You would have to:

  1. For now unfortunately all you can is to learn JavaScript
  2. Or wait for Elixir Framework Forums > Hologram Forum to become more functional (it does not support all events yet)

:+1:

2 Likes

Oh, so that would work? So for example I can write add id like my-editor-#{System.unique_integer()} (or something like that and then the changed assign is always used? I was thinking we need a JS hook for all component’s logic. :eyes:

Well, I got it to work without JS. Seems clunky to me but it works.

I added :search_mode to the data being passed down to the SearchSelect LV component. It tells that child LV component whether to clear or not clear the @search_input after it messages the parent LV with the collected data.

So it’s a simple condition: if select_mode is :search, assign an empty string to search_input, else leave it alone.

Bottom line: If I set @search_input to an empty string in the child LV component, the <input value> is updated to reflect the new @search_input assign (an empty string). If it relies on waiting for the update on @search_input assign from the liveview, then it doesn’t reflect that updated assign value (it continues to reflect that last entered value).

1 Like

You should assign it only when that input changes otherwise you can run into some weirdness, like it re-rendering if other parts other parts other part of the same template re-render.

1 Like

Amazing! Now I have to figure out how to not lose the focus (not just set default focus) and edit position in textarea … Many thanks!