Form submit with turbo-streams response, without redirect

Even though I’ve done turbo with forms and thought I understood the limitations, I’m having trouble getting this to work the way I want.

I have a page with two turbo frames, “mapping” and “example”. Mapping contains a form which is a list of select dropdowns. Stimulus is used to have each select’s “change” event submit the form.

Form submits fine, and then my controller returns two turbo stream frames to replace the “mapping” and “example” frames. This renders fine, but what I’m seeing on the browser is that the submit is getting a redirect from the server, and then just renders the text of my turbo-streams, rather than replace the contents in the page. (Presumably because of the redirect.)

How do I stop turbo from redirecting after the form submit, and instead replace the frames on the page?

Here are the controller actions involved:

    def mapping
      @mapping_fields = mapping_fields
    end

    def save_mapping
      @mapping_fields = mapping_fields
      streams = [
        turbo_stream.replace('mapping', partial: 'mapping'),
        turbo_stream.replace('example', partial: 'example')
      ]
      render turbo_stream: streams
    end

Here is the partial with my form:

<div turbo-frame='mapping'>
  <div data-controller='mapping'>
    <%= form_with url: :app_imports_save_mapping, data: { mapping_target: 'form' } do |form| %>
    <table>
      <% @import.headers.each_with_index do |header, i| %>
      <tr>
        <td><%= header%></td>
        <td>
          <%= form.select "mapping_#{i}", @mapping_fields, { include_blank: true }, data: { action: 'mapping#submit' } %>
        </td>
      </tr>
      <% end %>
    </table>
    <%= form.submit 'Next' %>
    <% end %>
  </div>
</div>

And stimulus is just doing a form.submit.

Thanks!

1 Like

Should be form.requestSubmit. If you do form.submit, Turbo can’t intercept the submission and can’t understand the turbo-stream response from Rails.

Note that you’ll need a polyfillSafari doesn’t support requestSubmit.

4 Likes

Perfect, thank you! That worked exactly how I needed it as far as I can tell

Also, it looks like you can use:

Turbo.navigator.submitForm(this.formTarget)

and don’t need a polyfill in safari.

4 Likes

However, I just realized that when I’m submitting the form like this, even though I’m returning Turbo stream replace blocks, Turbo is not updating the frames on the page with the updated data. I added some validation messages and while I can see them in the response, Turbo isn’t updating the page to match.

Well, I’ve almost figured this out.

If I have Turbo.navigator.submitForm(this.formTarget) in my stimulus controller
And, I have my form action in my rails controller render the full template (which includes my two turbo frames) WITH

render :mapping, status: :accepted

Then the form saves, the new HTML comes down from the server, the two turbo frames get updated and everything looks great.

EXCEPT, when I do this, the url changes in the browser to the url of the form, which then causes an error if the user refreshes because there is no “get” on the form save action route.

This is feeling very “whack-a-mole” to me, and at this point I could have been done with it using traditional AJAX - but I’m trying to move to the new Rails Way.

What you’re running into is exactly why Turbo requires a redirect for form submissions.

What was wrong with render turbo_stream: streams, as you’d originally intended?

1 Like

When using the streams, turbo doesn’t update the frames, even though I’m sending them down and see them in my network tab.

This is when using either this.formTarget.requestSubmit or Turbo.navigator.submitForm(this.formTarget)

I’m thinking at this point I don’t have a good way to get the UX I want unless I just do AJAX calls and manage the response client side. Which is fine, I’m sure this is an edge case and not Turbo’s fault. I was hoping though that there was a way to make it work without additional JS.

1 Like

Hey Dave :wave: if you are looking for standard AJAX behavior with a UJS like interface while still being able to use Turbo / Turbo Streams, I could point you in the direction of Mrujs. (A library I created intended to be a drop-in replacement for rails-ujs)

https://Mrujs.com

If you have any questions, feel free to stop by the StimulusReflex discord and I’d be happy to help in anyway I can.

1 Like

Have a look a this post for a nice solution to this problem using request.js:

2 Likes

Is there any documentation for that method?

I also asked myself this question. But nevertheless it works great, as it should.