Skip to content

Instantly share code, notes, and snippets.

@ahmad19
Last active October 2, 2023 23:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ahmad19/9092275c6c87a8ac6699df9362b06023 to your computer and use it in GitHub Desktop.
Save ahmad19/9092275c6c87a8ac6699df9362b06023 to your computer and use it in GitHub Desktop.
Render inline errors in Rails forms after submit and hotwire changes
<%= form_for customer, html: { id: dom_id(customer), class: 'row g-3' } do |f| %>
<% presenter = InlineErrorRenderer.new(customer, self, 'form-control-lg form-control-solid mb-3 mb-lg-0') %>
<div class='col-md-6'>
<%= f.label :full_name %>
<%= f.text_field :full_name, presenter.html_options_for(:full_name) %>
<%= presenter.error_container_for(:full_name) %>
</div>
<div class='col-md-6'>
<%= f.label :phone_number, 'Phone Number' %>
<%= f.text_field :phone_number, presenter.html_options_for(:phone_number) %>
<%= presenter.error_container_for(:phone_number) %>
</div>
<div class='col-md-6'>
<%= f.label :email, 'Email' %>
<%= f.text_field :email, presenter.html_options_for(:email) %>
<%= presenter.error_container_for(:email) %>
</div>
<div class="col-md-6">
<%= f.label :country, 'Country' %>
<%= country_select(f, "country", { priority_countries: ['IN', 'US'], selected: "IN" }, presenter.html_options_for(:country)) %>
<%= presenter.error_container_for(:country) %>
</div>
<div class="col-12">
<%= f.submit 'Submit', class: 'btn btn-outline-secondary' %>
<%= link_to 'Back', customers_path, class: 'btn btn-outline-warning' %>
</div>
<% end %>
// Rails forms when in error state wraps each form element inside div with "field_with_errors" class.
// Therefore bootstrap error classes (invalid-feedback, is-invalid) won't work because of following structure.
// <div class="form-control">
// <div class="field_with_errors">
// <input type="text" class="is-invalid"
// </div>
// </div>
.invalid-feedback {
display: block !important;
}
.form-control.form-control-solid.is-invalid {
border-color: #F1416C;
}
# This is optional but if you want to use hotwire then just return the error response in turbo_stream in controller
def create
@customer.user_id = current_user.id
respond_to do |format|
if @customer.save
# do usual stuff
else
format.turbo_stream
end
end
end
# and handle that turbo_stream response in create.turbo_stream.erb
<%= turbo_stream.replace "new_customer", partial: "customers/form", locals: { customer: @customer } %>
# InlineErrorRenderer is based on TwitterBootstrap's server side form validations.
# https://getbootstrap.com/docs/5.0/forms/validation/#server-side
class InlineErrorRenderer
def initialize(model, template, default_classes)
@model = model
@template = template
@default_classes = default_classes
end
FORM_INVALID = '%s %s is-invalid'
ARIA = '%s_aria'
def html_options_for(attr, html_class = nil)
if errors[attr].present?
{
class: format(FORM_INVALID, default_classes, html_class),
aria: { describedby: format(ARIA, attr) }
}
else
{ class: default_classes }
end
end
def error_container_for(attr)
if errors[attr].present?
template.tag.div(class: 'invalid-feedback', id: format(ARIA, attr)) do
errors[attr].join(',').html_safe
end
end
end
private
attr_reader :model, :template, :default_classes
def errors
@errors ||= model.errors
end
end
@ahmad19
Copy link
Author

ahmad19 commented Jun 4, 2021

This is how it will be displayed after the form submit

form_errors

@ahmad19
Copy link
Author

ahmad19 commented Jun 6, 2021

With hotwire added, it stays on the same page customers/new and renders the errored state of the form.

Screen.Recording.2021-06-06.at.11.20.52.AM.mov

@vasilevskykv
Copy link

This code doesn't work. I got an error:
"cannot load such file -- I:/RB/proj/AskIt/app/controllers/InlineErrorRenderer.rb"

@ahmad19
Copy link
Author

ahmad19 commented May 10, 2023

This code doesn't work. I got an error: "cannot load such file -- I:/RB/proj/AskIt/app/controllers/InlineErrorRenderer.rb"

@vasilevskykv class InlineErrorRenderer is meant to be a service class and not a controller. Add that file in a new folder inside app called as service and then create a file with the name inline_error_renderer.rb and then paste that code. It should work then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment