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

1.4k views Asked by At

(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:

  • modifying the link path edit_translation_path(format: "turbo-stream") per this SO Q/A
  • modifying the route get(:edit, to: "translations#edit", as: "edit_translation", defaults: { format: :turbo_stream }) per this SO Q/A
  • adding Turbo.session.drive = true to app/javascript/application.js, per this issue
  • link_to(data: { turbo_method: :get } per this answer
  • Turbo.setFormMode("on") in app/javascript/application.js

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

1

There are 1 answers

3
Alex On BEST ANSWER

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 %>