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.
- Updated all of my apps and Elixir (I’m not won LV 1.0.9
- Installed Liveview Debugger which is BRILLIANT! (thank you to whoever developed that!)
- Traced all changes and realized I had race conditions with my handle_events
- 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:
- The user selects “Fur” from the dropdown, which updates the input field’s DOM state to “Fur”.
- LiveView updates @search_input to an empty string and re-renders the component.
- 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"
/>