ruby-on-railsturbo

Rails: Turbo Frame / Turbo Stream element is not rendered the SECOND time I trigger it


In a view that displays a list of categories, there is a little part underneath that shows the "default" of these categories. When a user clicks there, it is replaced by a drop down form where they can select the new default. Upon submitting of the form, the two parts of the page - list of categories as well as lower part where the default is shown - shall be updated via turbo stream.

This works once. If I try to click the lower element for the second time, the whole page renders blank even though the network tab of the browser shows that something is arriving. I don't understand why that happens.

view (index page):

<%= turbo_frame_tag 'settings-content' do %>
  <h3 class="heading4">Available book formats</h3>
  <ul class="simple-list width-33" id="book-formats">
    <%= render 'new_form_entry' %>
    <%= turbo_frame_tag 'format-list' do %>
      <%= render @book_formats %>
    <% end %>
  </ul>
  <h3 class="heading4 margin-top-1 margin-bottom-50">Default Book Format</h3>
  <ul class="simple-list width-25">
    <li>
      <%= turbo_frame_tag 'default-format' do %>
        <%= render 'book_formats/set_default_form' %>
      <% end %>
    </li>
  </ul>
<% end %>

controller (relevant two methods):

  def set_default
    @book_formats = BookFormat.all.order(:name)
  end

  def update_default
    @new_default = BookFormat.find_by(name: book_format_params[:name])
    @default_book_format.update(fallback: false)
    @new_default.update(fallback: true)
    @default_book_format = @new_default
    @book_formats = BookFormat.all.order(:name)
  end

update_default.turbo_stream.erb

<%= turbo_stream.replace 'format-list' do %>
      <%= render @book_formats %>
<% end %>
<%= turbo_stream.replace 'default-format' do %>
      <%= render 'book_formats/set_default_form' %>
<% end %>

I am sure I am missing a small piece somewhere?


Solution

  • turbo_frame_tag helper renders a <turbo-frame> tag and the form:

    <turbo-frame id="default-format">
      <form>
        <!-- ... -->
      </form>
    </turbo-frame>
    

    turbo_stream.replace will replace the target #default-format with the template, in this case it is just the form:

    <turbo-stream action="replace" target="default-format">
      <template>
        <form>
          <!-- ... -->
        </form>
      </template>
    </turbo-stream>
    

    Which means <turbo-frame> goes away:

    <!-- target      identified by id -->
    <!-- v           v                -->
    <turbo-frame id="default-format"></turbo-frame>
    
    <!-- is replaced by -->
    <form></form>
    
    <!-- end result -->
    <form></form>
    

    There is no #default-format for the stream to replace the second time.


    turbo_stream.update action keeps the target and only updates the contents.

    <!-- target      identified by id -->
    <!-- v           v                -->
    <turbo-frame id="default-format"></turbo-frame>
    
    <!-- target content is updated with -->
    <form></form>
    
    <!-- end result -->
    <turbo-frame id="default-format">
      <form></form>
    </turbo-frame>
    

    The target of the turbo-stream can be anything, not just turbo-frame:

    <!-- unless you're doing something else with this frame, 
         there is no need for it -->
    <%= turbo_frame_tag "format-list" do %>
      <%= render @book_formats %>
    <% end %>
    
    <!-- just an `id` is enough for turbo-stream -->
    <div id="format-list">
      <%= render @book_formats %>
    </div>
    

    https://turbo.hotwired.dev/reference/streams