Youâre always spot on with your insights @garrison, thanks for chiming in!
I would like to offer a hint for others (and myself in the future) with a possible path forward. First, summarizing the issue:
Issue summary
Server state didnât change?
- A form has
id, phx-change, phx-submit and phx-debounce. The latter makes it more likely that a submit event may reach the server before any change event, but I believe is not fundamental to trigger the issue.
- The initial form state is something like
to_form(%{}).
- The user submits the form, say with some text input with value
"hello".
- The server handles the submit event and responds with what is intended to be an empty form, again
to_form(%{}).
- From the serverâs perspective, there was no change, so no reason to re-render the text input.
- The text
hello remains visible

So in previous messages in this thread we talked about the âno change from the serverâs perspectiveâ. Thereâs a little bit more into play here, for which I refer to the LiveView documentation:
LiveView JS client specificsâŠ
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.
So even if in step 4 above weâd assign to_form(%{content: "some other value"}), LiveView would still not update the visible input value (assuming it would be focused when submitting the form).
Possible solution
After evaluating a few options, I decided to go with a âpessimistic UI updateâ in my particular case. It means I want the client to clear the input only after the event is handled in the server and there were no validation errors, etc.
Eventually, the change is small but combines different concepts, so I decided to share.
push_event/3 to send (JS) events from server to client, only in the success path.
- Global JS event listener to set DOM properties (unlike the
JS.set_attribute/3 command that works on HTML attributes).
defmodule MyApp.MyLive
def render(assigns) do
~H"""
<.form
for={@form}
id="my-form"
phx-change="validate"
phx-submit="send"
phx-debounce
class="phx-submit-loading:opacity-50"
>
...
</.form>
"""
end
# ...
def handle_event("send", %{"message" => message}, socket) do
socket =
case send(message) do
{:ok, message} ->
socket
|> assign(form: to_form(%{}, as: :message))
|> clear_input()
{:error, changeset} ->
socket
|> assign(form: to_form(changeset, action: :submit))
end
{:noreply, socket}
end
defp clear_input(socket) do
socket
|> push_event("myapp:setproperty", %{
"selector" => "##{socket.assigns.form[:content].id}",
"property" => "value",
"value" => ""
})
end
end
// app.js
// Similar to `JS.set_attribute/3` but sets a DOM property instead of an HTML
// attribute. The distinction is important for certain properties like `value`
// on input elements, which do not reflect changes made to the attribute.
// https://elixirforum.com/t/using-liveview-js-to-manipulate-input-values/46371/2?u=rhcarvalho
// https://hexdocs.pm/phoenix_live_view/syncing-changes.html#the-problem-in-a-nutshell
// https://hexdocs.pm/phoenix_live_view/form-bindings.html#javascript-client-specifics
window.addEventListener("phx:myapp:setproperty", event => {
const target = document.querySelector(event.detail.selector);
target[event.detail.property] = event.detail.value;
})
Outro
Other possible directions
-
âOptimisticâ UI updates with JS commands, like
phx-submit={
JS.push("send")
|> JS.dispatch(
"setproperty",
to: "#field-id",
detail: %{property: "value", value: ""}
)
}
This also works, instantly clears the text input (assuming an event handler for setproperty), and correctly recovers the submitted value in case of a validation error.
-
Client hooks with phx-hook, possibly using the new ColocatedHook in LV 1.1.
-
Listening for the phx:page-loading-stop event as part of the form submit flow.
-
Custom submit handler.
More on the LiveView docs
Quoting Form bindings â Phoenix LiveView v1.1.14, emphasis mine:
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. This works well for updates where major side effects are not expected, such as form validation errors, or additive UX around the userâs input values as they fill out a form.
I continued reading the page looking for âconversely, what do I do when major side effects ARE expected?â â not found 
If anyone wants to continue the discussion in this direction we might be able to fill in that gap in the docs together!
Thanks! 