I currently have an issue with using turbo frames to progressively enhance a form.
Context
I generally use turbo frames as a way to enhance the user experience by inline presenting forms that have their standalone pages. So my expectation (and why I use Turbo Frames) is that I can build a form —pretending that JS is disabled for the end user— and know that when the user clicks the link “Sign up”, it’ll navigate to the /sign-up page. Only after this is done and working will I then wrap the form in a turbo-frame tag and present it inline on user click in another context.
My specific issue
Inside my form, I have a submit button and a cancel button:
<%= form_with model: contact, url: target_url, local: true do |form| %>
<% errors = []
errors += contact.errors.full_messages if contact
errors += association.errors.full_messages if association
%>
<% required_fields ||= [] %>
<% if errors.any? %>
<div class="bg-red-50 rounded-md p-4 mt-4 border-red-200 border border-1">
<ul class="list-disc pl-6">
<% errors.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="space-y-12">
<div class="mt-10 grid grid-cols-1 gap-x-6 gap-y-4 sm:grid-cols-2">
<div class="">
<%= form.label :first_name, "First Name", class: required_fields.include?(:first_name) ? "font-bold block text-gray-700 text-sm mb-2" : "block text-gray-700 text-sm mb-2" %>
<%= form.text_field :first_name, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
</div>
<div class="">
<%= form.label :last_name, "Last Name", class: required_fields.include?(:last_name) ? "font-bold block text-gray-700 text-sm mb-2" : "block text-gray-700 text-sm mb-2" %>
<%= form.text_field :last_name, class: "shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" %>
</div>
<div class="flex gap-x-4">
<%= form.submit button_title, class: "btn-primary" %>
<%= cancel_button "Cancel", student_path(student) %>
</div>
</div>
<% end %>
This form is inside my new.html.erb file:
<div class="main-wrapper">
<h1 class="page-heading">Create a new user</h1>
<p>This should not be seen in turbo frame</p>
<div class="max-w-md">
<%= turbo_frame_tag dom_id @student, "add-parent" do %>
<p class="text-sm text-gray-500">Some copy here</p>
<%= render partial: "contacts/form", locals: {} %>
<% end %>
</div>
</div>
When a user clicks “Add parent” button, if JS is enabled, it should present the turbo-frame content inline. That works great. When they click Cancel, it correctly closes out the frame and replaces itself with the original content.
When the user navigates to the full page via URL, everything looks great. When the user clicks Cancel, it correctly navigates back to the correct page.
However, when the user navigates to the full page via URL, and submits the form with errors, the Cancel button does not navigate to the correct page with a full page refresh and instead treats the form as a turbo-frame and tries to replace its contents with a swap from the page that housed the turbo-frame tag. My controller code looks like:
def create
@student = Student.find(params[:student_id])
@contact = Contact.new(allowed_params.merge(organization: @student.organization))
@association = @student.parent_guardians.build(contact: @contact)
valid_contact = @contact.valid?
valid_association = @association.valid?
respond_to do |format|
if valid_contact && valid_association
ActiveRecord::Base.transaction do
@contact.save!
@association.save!
end
format.html { redirect_to @student, notice: "Successfully created #{@contact.full_name}" }
format.turbo_stream
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
Expected behavior
- when a user clicks Cancel —and the form was loaded as a turbo-frame— it simply replaces the content with the original content
- when a user clicks Cancel —and they are on the full page, not loaded via turbo-frame— it correctly navigates to the target URL with a full page refresh
My guess is that I’m doing something wrong that is very simple, but I’m not sure where to look.