Turbo-frames: Form is not removed after submission

Hi all, I’m currently using turbo frames to display an inline form. I have a button add unit that replaces a turbo frame tag with a form. After submission I would like the form to be replaced by the first turbo frame tag that loads the form.

 <div class="px-4 sm:px-6 lg:px-6 py-8">
      <%= turbo_frame_tag('new_unit') do %>
        <div class="sm:flex sm:items-center">
          <div class="sm:flex-auto">
            <h1 class="text-xl font-semibold text-gray-900">Units</h1>
            <p class="mt-2 text-sm text-gray-700">A list of all the units that belong to this property</p>
          </div>
          <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
            <%= link_to new_property_unit_path(@property), class:"inline-flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 sm:w-auto" do %>
              <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
                <path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
              </svg>
              Add Unit
            <% end %>
          </div>
        </div>
      <% end %>
      <div class="mt-8 flex flex-col">
        <div class="-my-2 -mx-4 overflow-x-auto sm:-mx-6 lg:-mx-8">
          <div class="inline-block min-w-full py-2 align-middle md:px-6 lg:px-8">
            <table class="min-w-full divide-y divide-gray-300">
              <thead>
                <tr>
                  <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6 md:pl-0">Unit Name</th>
                  <th scope="col" class="py-3.5 px-3 text-left text-sm font-semibold text-gray-900">Monthly Rent</th>
                  <th scope="col" class="py-3.5 px-3 text-left text-sm font-semibold text-gray-900">Rooms</th>
                  <th scope="col" class="py-3.5 px-3 text-left text-sm font-semibold text-gray-900">Status</th>
                  <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6 md:pr-0">
                    <span class="sr-only">Edit</span>
                  </th>
                </tr>
              </thead>
              <tbody class="divide-y divide-gray-200" id="unit_list">
                <% @property.units.each do |unit| %>
                  <%= render partial: 'properties/unit', locals: { unit: unit } %>
                <% end %>
              </tbody>
            </table>
          </div>
        </div>
      </div>
    </div>
    <!-- /End replace -->
  </div>
</div>

So I have a turbo_frame_tag('new_unit') within this turbo frame tag I have a button that links to a properties/:id/units/new. When I click on the link, the turbo stream tag is replaced with the form in views/units/form.

<%= turbo_frame_tag(dom_id(unit)) do %>
  <%= form_for [@property, @unit], html: { class: "space-y-8 divide-gray-200" } do |f| %>
    <div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
      <div class="pt-8 space-y-6 sm:pt-10 sm:space-y-5">
        <div>
          <p class="mt-1 max-w-2xl text-sm text-gray-500">A unit can be an apartment number or a floor</p>
        </div>
        <div class="space-y-6 sm:space-y-5">
          <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
            <label class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">
              <%= f.label :apartment_name, 'Unit name' %>
            </label>
            <div class="mt-1 sm:mt-0 sm:col-span-2">
              <%= f.text_field :apartment_name, value: "APT 4B", class: "block max-w-lg w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border-gray-300 rounded-md" %>
            </div>
          </div>
          <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
            <label class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">
              <%= f.label :status, 'Status' %>
            </label>
            <div class="mt-1 sm:mt-0 sm:col-span-2">
              <%= f.select(:status, ['vacant', 'rented'], {}, { class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" }) %>
            </div>
          </div>

          <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
            <label class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">
              <%= f.label :day_rent_due, 'Day of the month when rent is due' %>
            </label>
            <div class="mt-1 sm:mt-0 sm:col-span-2">
              <%= f.number_field :day_rent_due, in: 1..31, value: "Day the rent for the unit is due", class: "block max-w-lg w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm border-gray-300 rounded-md" %>
            </div>
          </div>

          <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
            <label for="city" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">
              <%= f.label :monthly_rent_cents, 'Monthly Rent' %>
            </label>
            <div class="mt-1 sm:mt-0 sm:col-span-2">
              <div class="mt-1 relative rounded-md shadow-sm">
                <div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
                  <span class="text-gray-500 sm:text-sm"> $ </span>
                </div>
                <%= f.number_field :monthly_rent_cents, value: 0.00, class: "focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-7 pr-12 sm:text-sm border-gray-300 rounded-md"%>
                <div class="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
                  <span class="text-gray-500 sm:text-sm" id="price-currency"> USD </span>
                </div>
              </div>
            </div>
          </div>
          <div class="sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:border-t sm:border-gray-200 sm:pt-5">
            <label for="country" class="block text-sm font-medium text-gray-700 sm:mt-px sm:pt-2">
              <%= f.label :rooms, 'Rooms' %>
            </label>
            <div class="mt-1 sm:mt-0 sm:col-span-2">
              <%= f.select(:rooms, ['studio', '1', '2','3', '4', '5+'], {}, { class: "max-w-lg block w-full shadow-sm focus:ring-indigo-500 focus:border-indigo-500 sm:max-w-xs sm:text-sm border-gray-300 rounded-md" }) %>
            </div>
          </div>
          <div class="sm:border-t sm:border-gray-200 sm:pt-5">
            <div>
              <%= f.label :notes, 'Add your notes & documents', class: "block text-sm font-medium text-gray-700" %>
              <div class="mt-1">
                <%= f.rich_text_area :notes, class: "shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md" %>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <div class="pt-5">
      <div class="flex justify-end">
        <%= link_to 'Cancel', property_path(@property), class: "bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" %>
        <%= f.submit "Save", class: "ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" %>
      </div>
    </div>
  <% end %>
<% end %>

When I submit the form, which created a unit instance. In the controller I add this unit to a list via the append method.

class UnitsController < ApplicationController
  def create
    @property = Property.find(params[:property_id])
    @unit = @property.units.create(unit_params)

    respond_to do |format|
      format.turbo_stream do
        render turbo_stream: turbo_stream.prepend(:unit_list, partial: "properties/unit",
          locals: { unit: @unit })
      end

      format.html { redirect_to unit_url }
    end
  end

However, the form stays rendered. Shouldn’t after submission the form be reseted to the previous state? It keeps updating the unit list. But I would like for the form after a submission to go back to it’s previous state . Any guidance on how to achieve that?

Turbo-frame requests expect a turbo-frame with a matching ID response, however this code is responding with a turbo-stream, so the turbo-frame will not update.

Two possible solutions might be:

  1. remove the turbo-stream response and redirect to a view which returns the turbo-frame in the state you want it reset. Include a nested turbo-stream tag inside the turbo-frame which will execute the prepend from the prior logic.

  2. Drop the turbo-frame for this feature and just use turbo-streams. You can return more than one turbo-frame in a single http request. The easiest way to do this would be to add a create.turbo_stream.erb and render the multiple turbo-stream elements in it.