Ajax forms submission. Switching from rails-ujs to Turbo & Stimulus

This post is about dealing with the Rails's new front-end solutions - Turbo and Stimulus which are also known as the core parts of Hotwire. Particularly, I will show you how you can handle Ajax form submission, compared to the old approach using rails-ujs.

Consider the following scenario:

  1. A user submits a form on a web page.
  2. The POST request is sent via Ajax to avoid reloading the page.
  3. After the request is processed on the server, some JS code is executed in the web browser based on the response.

For demonstration purposes I am going to use a call back form with two inputs.

<%= form_for :call_back_request do |f| %>
  <%= f.text_field :name, placeholder: "Your name" %>
  <%= f.phone_field :phone, placeholder: "Enter your phone number" %>

  <%= f.submit "Submit" %>               
<% end %>

Ajax forms with rails-ujs

rails-ujs has been included in Rails 5.1.0 and replaced with Hotwire in Rails 7. For older versions it can be installed as a Gem or as a JS package.

In any case it would require something like this in your application.js file:

require rails-ujs
// or
require("@rails/ujs")

To make the form remote (Ajax), we should add the remote: true option.

<%= form_for :call_back_request, remote: true do |f| %>
  <%= f.text_field :name, placeholder: "Your name" %>
  <%= f.phone_field :phone, placeholder: "Enter your phone number" %>

  <%= f.submit "Submit" %>               
<% end %>

When submitting the form if you look at the Rails console you will see that the request format is JS:

Started POST "/call_back_request"
Processing by CallBackRequestsController#create as JS

The controller may look like this:

class CallBackRequestsController < ApplicationController
  def create
    # processing request
    succeeded = [true, false].sample

    if succeeded
      render js: "alert('Thank you! We will call you soon!')"
    else
      render js: "alert('Sorry! Something went wrong.')", status: :internal_server_error
    end
  end
end

You can find an example project source code by this link: Rails Ajax Form with rails-ujs Example


Processing Ajax form submission with Turbo & Stimulus

Next code examples work in a newly created Rails 7 project, which comes with Hotwire by default.

Since Rails 7 doesn't include rails-ujs, we should remove the remote: true option from the form.

<%= form_for :call_back_request do |f| %>
  <%= f.text_field :name, placeholder: "Your name" %>
  <%= f.phone_field :phone, placeholder: "Enter your phone number" %>

  <%= f.submit "Submit" %>               
<% end %>

Now if you submit the form you will be able to see it in Rails console that the request format is TURBO_STREAM:

Started POST "/call_back_request"
Processing by CallBackRequestsController#create as TURBO_STREAM

So, sending back JS code will not work.

In this post example we don't need to use any TURBO_STREAM features. We only want some JS code to be executed. Let's change the controller to not return anything except a response status.

class CallBackRequestsController < ApplicationController
  def create
    # processing request
    succeeded = [true, false].sample

    if succeeded
      head :ok
    else
      head :internal_server_error
    end
  end
end

Now if you submit the form, the request will be processed correctly, but nothing will change in the browser. We have to add JS code to capture the response and show a notification like in the previous example. That's where Stimulus comes into play.

In a newly created Rails 7 project you can find the app/javascript/controllers/ directory containing Stimulus controllers. Let's add a new controller to handle the form submission.

// app/javascript/controllers/call_back_requests_form_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  connect() {
    this.element.addEventListener("turbo:submit-end", (event) => {
      if (event.detail.success) {
        alert("Thank you! We will call you soon!")
      } else {
        alert("Sorry! Something went wrong.")
      }
    })
  }
}

Add the following code in the app/javascript/controllers/index.js file to register the controller:

// app/javascript/controllers/index.js

import CallBackRequestsFormController from "./call_back_requests_form_controller"
application.register("call_back_requests_form", CallBackRequestsFormController)

Finally, connect the form to the Stimulus controller using the data-controller attribute:

<%= form_for :call_back_request, 
             html: { data: { controller: "call_back_requests_form" } } do |f| %>
  <%= f.text_field :name, placeholder: "Your name" %>
  <%= f.phone_field :phone, placeholder: "Enter your phone number" %>

  <%= f.submit "Submit" %>               
<% end %>

That's it!

You can see the project source code by this link: Rails Ajax Form with Stimulus Example

Unlike this post example, Ajax requests often result in some HTML elements being dynamically added/removed or updated on a web page. Historically, we have been doing that using JS like

// jQuery example
$('#my_container').html('<%= j render("my_partial") %>')

With Rails + Hotwire you shouldn't do that anymore. Hotwire's mission is to avoid writing JS code for dynamic HTML updates. So, if instead of just showing a notification I needed to update a web page content, I probably wouldn't use any JS code for that.

But that's a different story. The goal of this post is to show how you can execute a custom JS code after the form is submitted. This might be useful when implementing things like showing notifications, closing modal windows, and calling external JS libraries and APIs.
0
Comments (0)

Unknown user Sign in