Handle form errors with a stimulus class

We need to keep the focus in place when submitting the form. We are using a data-turbo-permanent for that. We were rendering the error form with a turbo stream. The problem was that turbo streams ignore permanent elements as per this https://github.com/hotwired/turbo/pull/688, so the system wasn't updating the input field as expected.

Handling this as the stimulus level will also help with managing additional error states (such as the one we need for confirmations).
This commit is contained in:
Jorge Manrubia
2025-05-07 08:46:47 +02:00
parent 6cb1762588
commit 55ea460d3e
5 changed files with 24 additions and 11 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ class CommandsController < ApplicationController
redirect_back_or_to root_path
end
else
render turbo_stream: turbo_stream.replace("commands_form", partial: "commands/form", locals: { error: true })
head :unprocessable_entity
end
end
@@ -2,10 +2,25 @@ import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static targets = [ "input" ]
static classes = [ "error" ]
// Actions
focus() {
this.inputTarget.focus()
}
handleSubmit(event) {
if (event.detail.success) {
event.target.reset()
} else {
this.#handleErrorResponse(event.detail.fetchResponse.response.status)
}
}
#handleErrorResponse(code) {
if (code == 422) {
this.element.classList.add(this.errorClass)
}
}
}
@@ -19,10 +19,6 @@ export default class extends Controller {
event.target.select()
}
reset(event) {
this.element.reset()
}
showPicker(event) {
if ("showPicker" in HTMLInputElement.prototype) {
event.target.showPicker()
+7 -5
View File
@@ -1,12 +1,14 @@
<% has_error = local_assigns.fetch(:error, false) %>
<%= form_tag commands_path,
id: "commands_form",
class: [ "flex align-center gap-half", { "has-error" => has_error } ],
data: { controller: "form", action: "turbo:submit-end->form#reset turbo:submit-end->commands#focus keydown.meta+k@document->commands#focus" } do %>
class: [ "flex align-center gap-half" ],
data: {
controller: "form commands",
commands_error_class: "has-error",
action: "turbo:submit-end->commands#focus keydown.meta+k@document->commands#focus turbo:submit-end->commands#handleSubmit"
} do %>
<label class="terminal__label txt-nowrap" for="fizzy_do">Fizzy do &gt;</label>
<%= text_field_tag "command", has_error ? params[:command] : nil,
<%= text_field_tag "command", nil,
autocomplete: "off",
autocorrect: "off",
autocapitalize: "off",
+1 -1
View File
@@ -1,5 +1,5 @@
<%= tag.div class: "terminal full-width flex flex-column gap justify-end", data: {
controller: "toggle-class commands",
controller: "toggle-class",
toggle_class_toggle_class: "terminal--open" } do %>
<%= turbo_frame_tag :recent_commands, src: commands_path, refresh: "morph" %>