ruby-on-railsturboturbo-rails

Rails `link_to` with data-turbo doesnt get TURBO_STREAM request format


(Original question: Does Turbo.setFormMode("optin") disable turbo requests in links? No, the link doesn't work even with Turbo.setFormMode("on"))

Mature app using Rails 6.1, recently added gem turbo-rails 1.5.0, gem importmap-rails 1.2.3 to start converting forms to Hotwire.

# app/javascript/application.js
import "@hotwired/turbo-rails"
Turbo.setFormMode("optin")

In a translation page erb template, I have links like this that should GET a turbo-stream request for a tiny edit form. The response should update a separate turbo-frame (or a div, have tried both), elsewhere on the page, replacing it with the edit form.

The issue is that these links are not sending request.format turbo-stream, they're sending request.format text/html. The links are built with a helper like this:

link_to(label, edit_translation_path(id: t_tag, locale: @lang.locale),
        data: { turbo: true, turbo_frame: "translation_ui",
                tag: t_tag, role: "show_tag" })

Controller action, nothing crazy, has worked for years with UJS:

  def edit
    @lang = set_language_and_authorize_user
    @tag = params[:id]
  rescue StandardError => e
    @msg = error_message(e).join("\n")
  end

The response template:

# app/views/translations/edit.erb

<%= turbo_stream.replace("translation_ui") do
  render(partial: "translations/form", layout: false)
  render(partial: "translations/versions", layout: false)
end %>

If I test the page in the browser, turbojs is being loaded, there are no js errors, the HTML is correctly built. Note that the "index" contains links to update a separate turbo-frame on the right, where the edit form should load. It doesn't need to be a turbo-frame — I've tried this as a div with an id.

<!-- app/views/translations/index.erb produces this html -->

<div id="translation_index">
  <a data-turbo="true" data-turbo-frame="translation_ui" 
     data-tag="hello" data-role="show_tag" 
     href="/translations/hello/edit?locale=en"><span class="tag">hello</span></a>
  <a data-turbo="true" data-turbo-frame="translation_ui" 
     data-tag="hello" data-role="show_tag" 
     href="/translations/hello/edit?locale=en"><span class="tag"> goodbye</span></a>
</div>
<turbo-frame id="translation_ui"></turbo-frame>

But clicking the link does not send a GET request.format == turbo_stream to the controller, the request.format is text/html or */*. This is what I get in the console:

Started GET "/translations/one/edit?locale=en" for ::1 at 2023-11-06 00:31:30 -0800
Processing by TranslationsController#edit as HTML
  Parameters: {"locale"=>"en", "id"=>"one"}

Turbo links work elsewhere in the app. Why is this happening?

Note there is Turbo.setFormMode = optin and I'm aware that setting data-turbo=false on any ancestors of the element would disable Turbo for all children, but that is not the case here.

Things I have tried:

WTF! None of this has any effect on the request.format sent from the links.


Solution

  • data-turbo-stream specifies that a link or form can accept a Turbo Streams response. Turbo automatically requests stream responses for form submissions with non-GET methods;
    data-turbo-stream allows Turbo Streams to be used with GET requests as well.

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

    GET links and GET forms need to have data-turbo-stream attribute to send a TURBO_STREAM request:

    <%= link_to "GET link", "/", data: {turbo_stream: true} %>
    
    <%= form_with url: "/", method: :get, data: {turbo_stream: true} do |f| %>
      <%= f.submit "GET form" %>
    <% end %>
    

    With Turbo.setFormMode("optin") you also have to specify data-turbo="true" for the form:

    <%= form_with url: "/", method: :get,
      data: {
        turbo: true,
        turbo_stream: true
      } do |f| %>
    
      <%= f.submit "GET form" %>
    <% end %>