Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the hotwire way of dealing with data-disable-with?

I'm starting to use Hotwire in my Rails app and I implemented something similar to the tutorial video where a form gets submitted and refresh another part of the page. Like in the video, I had to implement my own reset form controller:

import {Controller} from "stimulus"

export default class extends Controller {
  reset() {
    this.element.reset()
  }
}

to reset the values in the form. The form looks something like:

<%= form_with(model: @invitation,
              data: {controller: "reset-form",
                     action: "turbo:submit-end->reset-form#reset"}) do |form| %>
  <!-- other form elements -->
  <%= form.submit "Invite" %>
<% end %>

which generates in button like this:

<input type="submit" name="commit" value="Invite" data-disable-with="Invite">

Because there's a data-disable-with, when you press the button, it gets disabled to avoid double-click submissions, which is great. The problem is that this.element.reset() doesn't reset it.

What's the proper way of dealing with this?

I'm not searching for a workaround, I know many, but I'm searching for the clean solution to this problem.

Is this disabling of a button caused by UJS? does it mean UJS shouldn't be used in Stimulus application?

I can re-enable the button from the reset JavaScript function but if the input button is specified like this:

<%= form.submit "Invite", data: {disable_with: "Inviting, please wait..."} %>

then the original label (value) of the button is lost and I don't have a way to re-establish it, which makes me think whatever is implementing this functionality (UJS?) it's not designed for hotwire/spa applications, and expects a full reload of the page.

I could just throw:

config.action_view.automatically_disable_submit_tag = false

and implement my own double-click prevention with a Stimulus controller, but that feels wrong. The problem is not with the attribute data-disable-with but how it was implemented.

like image 361
pupeno Avatar asked Sep 13 '25 00:09

pupeno


1 Answers

Turbo has been disabling the submit button for a while now and more recently added data-turbo-submits-with as alternative to UJS data-disable-with:

# available since: turbo v7.3.0 (turbo-rails v1.4.0) thx @PaulOdeon
<%= form_with model: @invitation do |form| %>
  <%= form.submit "Invite", data: {turbo_submits_with: "Inviting..."} %>
<% end %>

Resetting the form is optional:

<%= form_with model: @invitation, data: {
  controller: "reset-form",
  action: "turbo:submit-end->reset-form#reset"
} do |form| %>
  <%= form.submit "Invite", data: {turbo_submits_with: "Inviting..."} %>
<% end %>
# NOTE: form reset is implemented as stimulus controller in the question

https://turbo.hotwired.dev/reference/attributes


Yes, you should just throw this in:

config.action_view.automatically_disable_submit_tag = false

it only adds data-disable-with attribute, which is UJS specific and doesn't affect Turbo.


However, it's not like it wasn't doable before. This is a simplified example if you want to implement your own custom behavior. To make a clear distinction here, I'm using non-turbo and non-ujs attributes (Turbo only sets and removes disabled attribute for us):

<%= form_with model: @invitation, data: {reset: true} do |form| %>
  <%= form.submit "Invite", data: {disable_append: "..."} %>
<% end %>

For simple, one shot actions you can skip Stimulus and only use Turbo events:

// app/javascript/application.js

document.addEventListener('turbo:submit-start', (event) => {
  const { detail: { formSubmission, formSubmission: { submitter } } } = event
  // disable-append or maybe disable-loading and make it spin
  if (submitter.dataset.disableAppend) {
    // TODO: handle <button> element and use innerHTML instead of value.
    // save original text
    formSubmission.originalText = submitter.value
    submitter.value += submitter.dataset.disableAppend
  }
})

document.addEventListener('turbo:submit-end', (event) => {
  const { detail: { formSubmission, formSubmission: { formElement, submitter } } } = event
  // put it back (formSubmission is the same object during form submission lifecycle)
  if (formSubmission.originalText) {
    submitter.value = formSubmission.originalText
  }
  // data-reset
  if (formElement.dataset.reset === 'true') {
    formElement.reset()
  }
})

That's pretty much how it's actually done in turbo now:
https://github.com/hotwired/turbo/blob/v7.3.0/src/core/drive/form_submission.ts#L217-L239

like image 154
Alex Avatar answered Sep 15 '25 13:09

Alex