Opting out of stream responses on a per form basis

Hi!

In the app I’m working on, there are two forms that post to the same Rails controller action. One of them wants a Turbo Stream response and the other wants a normal redirect.

Is there any way to opt out of Turbo Stream responses on a per form basis?

Thanks!

Sure. You can have format.turbo_stream and format.html responses on the same action.

I don’t there’s any blessed way to do this. Turbo Drive will add a header to accept a turbo stream when you submit a form. If you have this header, Rails will trigger your format.turbo_stream block and not format.html.

To get around this, you could turn turbo off for that form by adding a data-turbo="false" attribute. Or you could include something in the form itself — I’m thinking a hidden input — that you can pick up with an if/else block in your controller.

Thanks @strobilomyces and @dan!

The action does respond to both formats:

respond_to do |format|
  format.turbo_stream { render turbo_stream: turbo_stream.replace(@action) }
  format.html { redirect_to actions_path }
end

The problem is I only want the Turbo Stream response when submitting one of the forms. I do however want to submit both forms with Turbo, to make sure they work with Turbo Native.

A hidden input is a great idea! A similar (Rails-specific) solution I came up with is to explicitly specify the html format in the URL the form submits to:

form_with url: actions_path(@action, format: :html) do |f|

Even better, though, would be to be able to tell Turbo not to include the Turbo Stream content type in the Accept header. If I understand correctly, that happens here: turbo/form_submission.ts at 8bce5f17cd697716600d3b34836365ebcdc04b3f · hotwired/turbo · GitHub

Maybe the condition on line 114 could be changed to something like this

if (!request.isIdempotent && this.formElement.getAttribute("data-turbo-stream") != "false") {

Would that make sense? Am I using this the wrong way?

Ah, right, the accept header nonsense. Sorry. Your form_with solution looks good to me.

As for changing the behavior in Turbo itself, I have no idea whether that would be desirable; you could always submit a pull request.

I recently came across this issue and ended up adding support for data-turbo-stream="false" by using the following document wide event listener:

document.addEventListener("turbo:before-fetch-request", (event) => {
  if (event.target.dataset["turboStream"] == "false")
    event.detail.fetchOptions.headers["Accept"] = "text/html, application/xhtml+xml"
})

You use it like so:

<form ... data-turbo-stream="false">...</form>