Turbo is a great way to build user interfaces, but most Turbo forms have to wait for the server response. Here’s how I am adding a small loading spinner to the submit buttons to improve the UX.
Submit feedback
Whenever we submit a Turbo form, we are waiting for a response from the server without any big visual changes. This is especially noticable inside modals and on slow connections.
To improve the situation we’ll create a small Stimulus controller that can be attached to any such form and suggests everything is working despite the wait:
Here’s the implementation of our small controller for lazy forms:
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="lazy-form"
export default class extends Controller {
static targets = ["button"];
submit(event) {
const button = this.buttonTarget;
// Preserve the button's current width and height
const buttonWidth = button.offsetWidth + "px";
const buttonHeight = button.offsetHeight + "px";
button.style.width = buttonWidth;
button.style.height = buttonHeight;
// Change the button text and disable it
button.innerHTML = '<span class="small-loader"></span>';
button.disabled = true;
}
}
The controller’s only job is to update the button without any interference to the submission process. I find it best when the button’s width doesn’t change, but you can change it to whatever’s needed.
You might also want to style the disabled state e.g. with cursor: wait;
at least.
This can then be added to any form at hand:
<%= form_tag resource_path, method: :post, data: { turbo: true, controller: "lazy-form", action: "submit->lazy-form#submit" } do %>
...
<%= button_tag "Submit", class: "button is-primary", data: { "lazy-form-target": "button" } %>
<% end %>
Alternatives
There is also data-turbo-submits-with
which let’s you avoid writing Stimulus controller for simple cases.
Get Test Driving Rails and make your tests faster and easier to maintain.