When having a form such as e.g. the following:
<form phx-change="validate-on-change">
<input
name="some[nested][field]"
phx-value-target="some[nested][field]"
phx-blur="validate-on-blur"
value="<%= prettify(@my_field) %>"
/>
</form>
we edit the field, and then click on or alt+tab to another field in the form.
pretty-printing logic in prettify is not applied to the contents of the input field.
I would expect the prettifying to work when a phx-blur-event is handled.
As per the documentation:
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.
The phx-blur event is triggered the moment an element loses focus, so it does not make sense that the value is still not overwritten by LiveView.
Can you put together a minimal example? I cannot recreate this:
def render(assigns) do
~L"""
<form phx-change="suggest" phx-submit="search">
<input type="text" name="q" value="<%= @query %>" phx-blur="blur"/>
</form>
"""
end
def mount(_params, _session, socket) do
{:ok, assign(socket, query: "")}
end
def handle_event("suggest", %{"q" => query}, socket) do
{:noreply, assign(socket, :query, query)}
end
def handle_event("blur", _, socket) do
{:noreply, assign(socket, :query, "blurred!")}
end
Thanks!
Here you go:
defmodule LiveviewBlurExampleWeb.PageLive do
use LiveviewBlurExampleWeb, :live_view
@impl true
def mount(params, session, socket) do
new_socket =
socket
|> assign(:myfield, 0)
|> assign(:myfield2, 0)
{:ok, new_socket}
end
@impl true
def handle_event("validate-on-change", params = %{"myfield" => str_value}, socket) do
IO.inspect({"validate-on-change", params})
new_socket =
socket
|> validate_and_update_field(:myfield, str_value)
{:noreply, new_socket}
end
def handle_event("validate-on-change", params = %{"myfield2" => str_value}, socket) do
IO.inspect({"validate-on-change2", params})
new_socket =
socket
|> validate_and_update_field(:myfield2, str_value)
{:noreply, new_socket}
end
def handle_event("validate-on-blur", params = %{"value" => str_value, "target" => "myfield"}, socket) do
IO.inspect({"validate-on-blur", params})
new_socket =
socket
|> validate_and_update_field(:myfield, str_value)
{:noreply, new_socket}
end
def handle_event("validate-on-blur", params = %{"value" => str_value, "target" => "myfield2"}, socket) do
IO.inspect({"validate-on-blur2", params})
new_socket =
socket
|> validate_and_update_field(:myfield2, str_value)
{:noreply, new_socket}
end
defp validate_and_update_field(socket, name, str_value) do
case Integer.parse(str_value) do
{value, ""} ->
socket
|> assign(name, value)
_other ->
socket
end
end
def prettyprint(integer) when integer > 0, do: "+#{integer}"
def prettyprint(integer), do: to_string(integer)
def render(assigns) do
~L"""
<form phx-change="validate-on-change">
<input
name="myfield"
phx-value-target="myfield"
phx-blur="validate-on-blur"
value="<%= prettyprint(@myfield) %>"
/>
</form>
<form phx-change="validate-on-change">
<input
name="myfield2"
phx-value-target="myfield2"
phx-blur="validate-on-blur"
value="<%= prettyprint(@myfield2) %>"
/>
</form>
"""
end
end
Hey @chrismccord! Is there any further help I can provide at this time? :slightly_smiling_face:
The problem is that validate_and_update_field is called twice: once by the phx-change event and once by the phx-blur event. So, once it gets called the 2nd time, the variable since it was already "prettyfied" by the 1st call, will be the same, thus in LiveView eyes it didn't changed, so its key under the :changed map in the socket will be changed to false. Which is why it won't change in HTML.
With phx-change events on the form, use phx-debounce="blur" instead on the input fields.
- phx-blur="validate-on-blur"
+ phx-debounce="blur"
Thank you for your explanation!
As for your proposed change: wouldn't using phx-debounce="blur" mean that the event will _only_ trigger on blur?
In my actual application (that is obviously more involved than this self-contained example) I use the values from the input fields to drive a visualization (in this case: points on a colour wheel) which I definitely want to update _both_ on change and on blur.
In that case, keep the phx-blur event, but you'll need to figure a way to only assign it once when both events will be triggered.
On each new assign, the key's boolean value inside the :changed map is updated if the value changed from its previous value.
@chrismccord I still believe that a form-field visible value not updating on blur even if it was modified by earlier events should be considered unwanted behaviour.
I hope that this behaviour might be altered if at all possible, or otherwise that there is a manual way to indicate that a value needs to be sent again in response to an event.
@Qqwy I ran into a similar issue and this patch fixed it for me: #1070. please give it a try (please nuke node_modules after updating deps).
@josevalim Thank you very much! Just so you're aware, the first moment I have time to try this out is this Saturday; apologies for the delay.
@josevalim I have pointed my live_view version to {:phoenix_live_view, github: "phoenixframework/phoenix_live_view", branch: "jv-replace-el-on-undorefs", override: true}, removed the assets/node_modules folder, ran mix deps.get and then cd assets; npm install.
After that I tried out the application again, with the same result as before: the value of the input element will not be restored by a phx-blur-event (on the input element) that produces the same value as an earlier phx-change-event (on the encompassing form).
Is there something I have missed in your instructions?
So that PR doesn't fix it. Thanks for checking!
There's a couple things going on on this one. First, your example races the phx-change and blur events, so imagine the following input:
1234 => phx-change %{"myfield" => "1234"} => assign(:myfield, "1234")
The user still has the input focused, so their input is not prettified. Next the user blurs:
1234 => phx-blur %{"myfield" => "1234"} => assign(:myfield, "1234")
No diff is sent to the client, because nothing changed. The assign calls produced an identical socket, so the prettify functions are not run. To get what you want, you could introduce a new epoch assign on mount:
|> assign(epoch: 0)
and bump this value when you blur:
case Integer.parse(str_value) do
{value, ""} ->
socket
|> assign(name, value)
|> update(:epoch, &(&1 + 1))
_other ->
socket
end
~L"""
<form phx-change="validate-on-change">
<input
name="myfield"
phx-value-target="myfield"
phx-blur="validate-on-blur"
value="<%= @epoch && prettyprint(@myfield) %>"
/>
</form>
"""
....
This forces change tracking to occur. We could introduce a way to force a change to happen, but I'm not yet convinced that's the best step, because I think this usecase is actually best suited to be done on the client as this is really close to controlled input behavior. This would be a couple LOC hook, or using alpine.js a couple lines in your markup:
<input x-data="{}"
x-on:input="$event.target.value = `+${parseInt($event.target.value) || ''}`"
name="myfield"
.../>
Thanks!
Thank you for this in-depth investigation. I like the epoch approach to mitigate this issue much more than what I did before to circumvent this issue, which was a toggle_changes-boolean that got flipped and then flipped back.
I very much agree that it has to be seen what, if any, approach to tackle this issue should become part of LiveView itself.
I definitely think that other people will encounter similar issues, and hopefully this can serve as a bit of a reference for them.
:heart_eyes: !
Most helpful comment
@Qqwy I ran into a similar issue and this patch fixed it for me: #1070. please give it a try (please nuke node_modules after updating deps).