Hotwire Discussion

Performing client-side validations on submit-start and cancel form submission

I’m having an issue with cancelling/stoping a form from being submitted to the back end after failing some client-side validations. I am using Stripe-Elements which performs some client-side validations for me before I want to submit the form (with the stripe token) to my backend.

I tried to do the following:

// new.html.haml
= form_for ..., { data: 'turbo:submit-start->stripe#submitStart' }
  ...

// stripe_controller.ts
...
submitStart = (event: CustomEvent) => {
  if (valid) {
  } else {
    event.preventDefault();
    event.stopPropagation();
  }
}
...

However, this still submits the form to the backend despite failing client-side validations.

Is there a way to prevent form submission via this method?

Try replacing event.stopPropagation() with event.stopImmediatePropagation()

@tleish I went ahead and tried that and the form is still being submitted to the backend.

I believe the issue is that the submit-start event will stop the event from bubbling but there is a global submit listener on the form itself that will continue to execute. I don’t think there is a mechanism to support this in Turbo yet?

Which is executed first?

@tleish The order follows:

  1. submit listener (I throw a debugger in the dist source)
  2. turbo-submit-start

I’ve found a workaround in my case following some of the ideas in the Triggering Turbo Frame with JS thread.

  1. I add date-turbo: false to the form
  2. I add a submit->stripe#submit handler
  3. Call preventDefault() to stop normal browser submission
  4. Check validations
  5. If valid, manually trigger a form submission.

e.g.

// html
= form_with @object { data: { trubo: :false, action: 'submit->stripe#submit' }

// strip_controller.ts
submit(event) {
  event.preventDefault()

  if (valid) {
    navigator.submitForm(...) //manually trigger form submission (runs events like submit-start & submit-end)
  }
}

If the Stripe-Elements listener executes before the stimulus controller, it’s too late to call preventDefault();