6

I'm attempting to upgrade to Turbo from Turbolinks and I've found that the client is not rendering redirects for form submissions.

Versions:

  • rails 6.1.4
  • hotwire-rails 0.1.2
  • @hotwired/turbo-rails 7.0.0-beta.8

I've ignored the incompatibility between Turbo and Devise for now - just trying to get regular forms working without having to disable Turbo on them.

Here's an example action:

def update
  authorize @label
  @label.update(label_params)
  if @label.save
    redirect_to document_labels_path(document_id: @document.id)
  else
    render :new, status: :unprocessable_entity
  end
end

Here's a rendered form:

<form class="simple_form new_label" id="label_form" novalidate="novalidate" action="/documents/72/labels" accept-charset="UTF-8" method="post">
...
</form>

When submitting a valid form, the server will say Processing by LabelsController#create as TURBO_STREAM and correctly serve a 302. It will then serve the 200 for the redirect location. The browser however is left just looking at the submitted form. Changing the redirect status to 303 doesn't change anything.

I added a console.log for every Turbo event:

document.addEventListener("turbo:load", function () {
  console.log('TURBO:LOAD')
})
document.addEventListener("turbo:click", function () {
  console.log('TURBO:CLICK')
})
document.addEventListener("turbo:before-visit", function () {
  console.log('TURBO:BEFORE-VISIT')
})
document.addEventListener("turbo:visit", function () {
  console.log('TURBO:VISIT')
})
document.addEventListener("turbo:submit-start", function () {
  console.log('TURBO:SUBMIT-START')
})
document.addEventListener("turbo:before-fetch-request", function () {
  console.log('TURBO:BEFORE-FETCH-REQUEST')
})
document.addEventListener("turbo:before-fetch-response", function () {
  console.log('TURBO:BEFORE-FETCH-RESPONSE')
})
document.addEventListener("turbo:submit-end", function (event) {
  console.log('TURBO:SUBMIT-END')
  // event.detail
})
document.addEventListener("turbo:before-cache", function () {
  console.log('TURBO:BEFORE-CACHE')
})
document.addEventListener("turbo:before-stream-render", function () {
  console.log('TURBO:BEFORE-STREAM-RENDER')
})
document.addEventListener("turbo:render", function () {
  console.log('TURBO:RENDER')
})

This is what the output is for a successful form submission:

TURBO:BEFORE-FETCH-REQUEST
TURBO:SUBMIT-START
TURBO:BEFORE-FETCH-RESPONSE
TURBO:SUBMIT-END

There is no render event. Investigating event.detail.fetchResponse.response for turbo:submit-end it seems to be perfectly aware that the client should redirect, it just didn't.

Response {type: "basic", url: "http://lvh.me:3000/documents/72/labels", redirected: true, status: 200, ok: true, …}
body: (...)
bodyUsed: true
headers: Headers {}
ok: true
redirected: true
status: 200
statusText: "OK"
type: "basic"
url: "http://lvh.me:3000/documents/72/labels"
__proto__: Response

Update: It is actually performing the redirect and the server is generating the response. The issue is that the client is not rendering the redirect response.

Archonic
  • 5,207
  • 5
  • 39
  • 55
  • I have also tried enabling Turbo handling with `data-turbo="true"` which disables rails-ujs form submission handling, but again, no redirect is happening after a successful request. – Archonic Jul 27 '21 at 18:17
  • I think you're in an in-between state, which is why it doesn't work. Are you just trying to get form submission to work like before? Or are you trying to handle your form submission using turbo? Your controller doesn't distinguish between `format.html` and `format.turbo_stream` requests, which it needs to do if both are options. If you just want it to work like before Turbo, disable turbo on the submit button. There are several questions about links being "broken" after integrating Hotwire. https://stackoverflow.com/questions/68641396/rails-hotwire-why-does-my-link-disappear-when-i-click-it – aidan Aug 05 '21 at 18:47
  • I am trying to get the behavior to what it was at before Turbo without having to disable Turbo. If I have to disable it everywhere to get it working, I'd rather not implement it. So all form submissions need to be handled with a `format.turbo_stream` format in their action? That's not what I was reading. If it's a redirection it should redirect. Turbo does seem aware that it should be doing that. – Archonic Aug 05 '21 at 19:11
  • FWIW I have a login page that's pretty close to the default Devise session/new page. I have not needed to disable Turbo on my form because the form isn't inside of a turbo_frame tag. Turbo is mostly just opt-in. If you're getting a TURBO-STREAM request type, your form is probably inside of a turbo_frame. If not, I'm not sure why the request type is getting set as turbo. – aidan Aug 05 '21 at 19:19
  • That's a good hint - I am getting a turbo request from a form that's not inside a turbo frame. I gave up on trying to implement Turbo while it's in beta but I'll come back to this question when I have a fix. – Archonic Aug 06 '21 at 01:10
  • Thanks a lot, this cost me hours of confused debugging. – Alexander Presber Feb 12 '22 at 15:13
  • Over a year later I think I might finally understand the issue https://github.com/hotwired/turbo-rails/issues/122#issuecomment-1213225606 I'm using haml in this app and all my templates are `.haml` instead of `.html.haml`. The lack of `.html` may be what's changing the response content_type and causing Turbo to miss rendering the response. I'll investigate soon. – Archonic Aug 12 '22 at 18:38

3 Answers3

13

What is happening here is that your application is specifying that it prefers turbo-stream responses over text/html responses. If you were to look at your request headers for the redirect page, you'll likely see the following:

Accept: text/vnd.turbo-stream.html, text/html, application/xhtml+xml

As a result, Rails returns the data with the first type it recognizes, which is text/vnd.turbo-stream.html. Turbo in your browser sees this and, since it's not interpretable as a Turbo Stream, unhelpfully ignores it quietly.

The solution (workaround?) is to make sure you are redirecting to the html version of your page:

redirect_to document_labels_path(document_id: @document.id, format: :html)

This will return the page with a Content-Type of text/html, and Turbo will replace the whole page with the contents.

Jeff Seifert
  • 328
  • 1
  • 8
  • 1
    Wow, that's very subtle but makes sense. Is there a way to just change the accept line to `Accept: text/html, text/vnd.turbo-stream.html, application/xhtml+xml` instead of writing `format: :html` everywhere? I'll give this a shot later and mark it as accepted if it works. – Archonic Aug 06 '21 at 17:17
  • Finally got back to this issue. You were right. Generally this issue can be solved by using a `turbo_frame_tag` and ensuring that the response is in an HTML format block. If you run into this issue with Devise, my advice is disable turbo on Devise forms until there's an official released solution within Devise and Responders gems. – Archonic Oct 14 '21 at 01:38
  • Ok, interesting twist. Redirecting within a `format.html` block isn't enough. Specifying the format as `:html` within the redirect does actually change the behaviour within a format.html block. The console has `Response has no matching element` however. If you're creating a new resource then there wouldn't be a turbo_frame_tag with `new_resource`. Still not sure how to upgrade those forms. – Archonic Oct 15 '21 at 03:03
  • Adding the format only works if the response has a turbo-frame, which I don't. It's a redirect to the show or index. Everywhere I look it's more confirmation that not rendering the redirect from a form submission is confusing behaviour. It's plastered all over the Hotwire discuss and turbo-rails issues. – Archonic Oct 16 '21 at 00:26
  • 1
    @Archonic Still running into this issue three months later, working with a brand-new Rails 7 app. Pretty dismaying that this is (what I'd consider) broken out of the box. I found that the minimum I need to do to get redirects on `create` form submissions working is to wrap the `render: show` inside a `format.html` block. But like you, I'd rather not have to remember to do this anywhere. Did you ever discover a way to avoid this? Some kind of config we're missing that would respond with `text/html` by default instead of `text/vnd.turbo-stream.html`? – awolfson Dec 31 '21 at 20:07
  • 2
    @awolfson The most common suggestion I see is to make sure that the redirect status is 303 with `redirect_to whatever_path, status: :see_other`. That's never worked for me and I don't know why people keep suggesting it. In my main app I'm still disabling turbo on all forms which have a potential redirect response - big disappointment. This PR is working on a sensible way to catch the issue https://github.com/hotwired/turbo/pull/445. There's also this horrible hack which I don't recommend: https://github.com/hotwired/turbo/issues/138#issuecomment-944711809 – Archonic Jan 02 '22 at 01:40
  • Thanks @Archonic. I also tried redirecting 303 and that also failed for me. Will keep an eye on it! – awolfson Jan 04 '22 at 22:24
  • I have just text response before and my app can't redirect, after adding html element it works flawlessly – buncis Sep 04 '22 at 05:57
3

Jeff's answer is correct but I wanted to share the specific fix for the issue I was having.

If you use HAML or Slim, I've seen it on more than one codebase where developers rename all template files .haml instead of .html.haml (same for Slim). It's never bitten me before using Turbo, but without .html in the filename, part of Rails won't know what format to serve a response in, so it defaults to the request format.

Turbo makes a turbostream request when submitting a form, but if the response is a redirect, it expects it to be text/html in order to render it. If it receives a turbostream response to a redirect request, Turbo just sits there doing nothing with no console errors or warnings (terrible default behavior IMO).

So if your templates do not include .html, just add it back and Turbo will render redirects. You may still need status: :see_other.

More information:

https://github.com/hotwired/turbo-rails/issues/122

https://github.com/hotwired/turbo-rails/issues/287

Archonic
  • 5,207
  • 5
  • 39
  • 55
2

Adding to the excellent answer of Jeff Seifert.

If you don't need turbo streams, you may also unregister the turbo-stream content type altogether by putting this into an initializer e.g. config/initializers/turbo.rb:

Rails.application.config.after_initialize do
  Mime::Type.unregister(:turbo_stream)
end
Alexander Presber
  • 6,429
  • 2
  • 37
  • 66