How does form_with know to use content type "text/html; turbo-stream"

I’m working on an app with a “load more items” functionality where more things will get displayed on a list if a user clicks a button. Turbo stream append seems to be the appropriate tool for this, but I’m having trouble figuring out the Hotwire way of triggering the request. To get a better understanding I’ve taken a look at the Hotwire Rails Demo Chat and noticed that on the new message form, the form_with call always submits with the content type “text/html; turbo-stream” that tells Rails to process the POST message as a TURBO_STREAM. I don’t see any flags in the code where it specifies to do this. How is this magic happening?

Here’s what the form looks like for reference:

<%= turbo_frame_tag "new_message", target: "_top" do %>
  <%= form_with(model: [ @message.room, @message ],
        data: { controller: "reset_form", action: "turbo:submit-end->reset_form#reset" }) do |form| %>
    <div class="field">
      <%= form.text_field :content %>
      <%= form.submit "Send" %>
    </div>
  <% end %>
<% end %>

I tried replicating this form without the turbo_frame_tag and it somehow still knew to submit as a TURBO_STREAM. Could someone explain what’s happening here?

This doesn’t happen in Rails actually. Turbo handles this.

When you submit your forms with Turbo Drive, it adds an Accepts header to tell servers they can return a turbo stream in response. You can see the code here.

Rails already has logic to pick apart the Accepts header, which is what powers the respond_to method in your Rails controllers.

1 Like

Thanks for helping me find where this occurs in the code. It seems that the default convention now is that if Turbo is enabled, it is assumed that all form requests should be expected to be treated like a Turbo request by the backend Rails app in the respond_to responder. Am I understanding that correctly?

I can’t think of a circumstance right now why you’d want to turn it off and go back to a regular Accept: text/html type of form, but I’m taking that it’s not configurable?

yep that’s right: Turbo will add the header to all form submissions, unless they’re GET requests.

There’s no way to opt out, without disabling Turbo for the form, a la data-turbo=“false”.

2 Likes

Question:

Why do you use target: "_top"? Doesn’t that force your whole page to reload?