Why don't form fields clear when save is triggered by pushEventTo?

I have a form that I want to be automatically submitted when a user presses “Enter” inside of its textarea field, but I want shift+enter to add a newline. So I setup a hook for this that includes this listener function:

function listener(event) {
  if (event.key == "Enter" && !event.shiftKey && !event.repeat) {
    event.preventDefault()
    this.pushEventTo(this.el, "textarea-submit")
  }
}

and I have some handle_event branches that look like this:

  def handle_event("textarea-submit", params, socket) do
    save_comment(socket, socket.assigns.action, socket.assigns.form.params)
  end

  def handle_event("save", %{"comment" => comment_params}, socket) do
    save_comment(socket, socket.assigns.action, comment_params)
  end

They work exactly how you’d expect except that if the comment is saved via the “save” event getting triggered, the textarea gets cleared like you’d expect. If it is saved via the “textarea-submit” event, the comment saves just fine, but the textarea retains the value that was there before submission.

Any ideas why this might be happening? I don’t think it’s anything in the handle_event code because the “textarea-submit” branch works fine if I change the form element’s code to phx-submit="textarea-submit". It seems to be something about the fact that it’s triggered from the JavaScript side.

The textarea field code looks like this:

          <.input
            field={@form[:content]}
            id={new_comment_content_field_id(@post)}
            type="textarea"
            phx-hook="EnterSubmit"
            />

Update: it works fine if I change this

function listener(event) {
  if (event.key == "Enter" && !event.shiftKey && !event.repeat) {
    event.preventDefault()
    this.pushEventTo(this.el, "textarea-submit")
  }
}

to this

function listener(event) {
  if (event.key == "Enter" && !event.shiftKey && !event.repeat) {
    event.preventDefault()
    const newEvent = new MouseEvent("click")
    event.target.form.querySelector("button").dispatchEvent(newEvent)
  }
}

so it really does seem to be something with pushEventTo specifically.

That’s expected behavior as LiveView intentionally refrains from overwriting focused inputs.

JavaScript 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.

source: Form bindings — Phoenix LiveView v0.20.2

3 Likes

Have you considered the form’s usability for mobile users, where there is no shift-enter?

Overriding enter as new line seems counterintuitive to the generally expected UX of textareas and would probably cause friction and frustration for most users.

1 Like

That’s a good point; I think I’ll change it to control/command + enter. There’s also a button to do it for places where the shortcuts aren’t an option.

1 Like

Also I was looking into the original question of programmatically submitting a form and found that there are two form submit functions, submit() and requestSubmit(). I wasn’t aware of the second one, which forces the browser to run client-side validations, vs. plain submit which just submits without validation. Not sure how this plays with LiveView, but something to be aware of?

So in your hook you can probably capture and prevent default on ctrl/cmd-enter and just call this.el.form.requestSubmit() without needing to create and dispatch a custom event, or push_event back to the server.

1 Like

Ooh, thank you for this! requestSubmit does seem to play very nicely and it feels much less awkward than the click-event simulation I was doing before.

1 Like

One caveat for requestSubmit() is that it’s only supported on Safari >= 16, which, as of this post, is less than a year old. Still, browser coverage is pretty good at 89.75%

1 Like