39

I have a form I want to submit automatically whenever any input field is changed. I am using Turbo Streams, and if I use onchange: "this.form.submit()" it isn't captured by Turbo Streams and Rails uses a standard HTML response. It works fine when clicking the submit button. How can I work around this?

2 Answers 2

85

There is a discussion on the hotwire forum, where Mark Godwin figured out why form.submit() isn't working with turbo:

Turbo intercepts form submission events, but weirdly, the JS formElement.submit() method does not trigger the submit event.

And Jacob Daddario figures out that you can use form.requestSubmit() instead:

It turns out that the turbo-stream mechanism listens for form submission events, and for some reason the submit() function does not emit a form submission event. That means that it’ll bring back a normal HTML response. That said, it looks like there’s another method, requestSubmit() which does issue a submit event.

So you can change your code slightly, and use requestSubmit() if a browser supports it, and use submit() if not:

onchange: "this.form.requestSubmit ? this.form.requestSubmit() : this.form.submit()" 


Update:

As BenKoshy pointed out, in Turbo 7.1.0, a polyfill was added so you can use form.requestSubmit() without checking for browser support, so you can add this to your input field:

onchange: "this.form.requestSubmit()" 
Sign up to request clarification or add additional context in comments.

5 Comments

I confirm this works.
There's a polyfill which handles the browser compatibility issue out of the box, so long as you install the latest version of turbo, you'll be fine.
this works if you posting form. but if you have form_with( ... method: :get ) it will be submitted in html format. to fix this behavior you can use form_with(... data: { turbo_stream: true }). This is for turbo-rails 1.4.0 which is latest a.t.m.
There's nothing weird about form.submit() not emitting the event. A standard pattern is the "submit" event is listened for, something done, then the form submitted from within the event handler. While it's possible to mitigate, the standard functionality would be an infinite loop.
Works in Rails 8
5

I need to implement this for an app with lots of forms. I wound up using Stimulus. Below is the whole controller:

import { Controller } from "stimulus" const _ = require("lodash") export default class extends Controller { connect() { let that = this; that.element.addEventListener('change', _.debounce(that.handleChange, 500)) } handleChange(event) { event.preventDefault() // event.target.name // => "user[answer]" // event.target.value // => <user input string> event.target.form.requestSubmit() } } 

and here it's used in a form with a single text input. NOTE the controller is attached to the form, not to the inputs.

<%= turbo_frame_tag dom_id(form_model) do %> <%= form_with model: form_model, format: :turbo_stream, html: { data: { controller: "buttonless-form" } } do |f| %> <%= f.hidden_field :question_id, value: question.id %> <%= f.text_field :answer_value, class: "input shadow wide", placeholder: "Enter your answer here" %> <% end %> <div id=<%= "question_#{question.id}_output" %>> <p> <!-- feedback to the user shows up here via Turbo --> </div> <% end %> <!-- end turbo frame --> 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.