Rails 7 Form submission with Turbo Stream || Showing error on failure of form submission

Kanhaiya Dubey
4 min readJun 19, 2024

--

Introduction

Turbo Stream, part of the Hotwire suite, provides an elegant way to handle real-time updates in Rails applications. With Rails 7, using Turbo Stream to manage form submissions has become even more powerful and streamlined. In this blog post, we’ll explore how to handle errors gracefully when a form submission fails, providing a seamless user experience.

Prerequisites

Before we dive in, ensure you have the following:

  • A Rails 7 application set up.
  • Turbo and Stimulus libraries included in your project (these are included by default in new Rails 7 apps).

Step 1: Set Up Your Model and Controller

Let’s start by setting up a simple model and controller for demonstration. We’ll create a Post model with a title and body, and handle form submissions in the PostsController.

Generate the Model and Controller

rails generate model Post title:string body:text
rails generate controller Posts
rails db:migrate

Define Validations

In the Post model (app/models/post.rb), add some validations:

class Post < ApplicationRecord
validates :title, presence: true
validates :body, presence: true
end

Step 2: Create the Form

Next, we’ll create a form to submit new posts. In the PostsController, define the new and create actions:

class PostsController < ApplicationController
def new
@post = Post.new
end

def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: 'Post was successfully created.'
else
render :new, status: :unprocessable_entity
end
end

private

def post_params
params.require(:post).permit(:title, :body)
end
end

Step 3: Create the Views

Form Partial

Create a form partial (app/views/posts/_form.html.erb):

<%= form_for post, html: { class: 'row' } do |f| %>
<div class="col-auto">
<%= f.label :title, class: 'form-label' %>
<%= f.text_field :title, class: 'form-control' %>
</div>
<div class="col-auto">
<%= f.label :body, class: 'form-label' %>
<%= f.text_area :body, class: 'form-control' %>
</div>
<div class="col-auto mt-4">
<%= f.submit class: 'btn btn-primary' %>
</div>
<% end %>

Create a form partial (app/views/posts/new.html.erb):

<h2>Creating an Post</h2>
<%= render 'form', post: @post %>

Now we will get an error in rails 7
When submit the form to create new post.

turbo.es2017-esm.js:2115 Error: Form responses must redirect to another location at FormSubmission.requestSucceededWithResponse (turbo.es2017-esm.js:679:27) at FetchRequest.receive (turbo.es2017-esm.js:450:27) at FetchRequest.perform (turbo.es2017-esm.js:431:31)
turbo.es2017-esm.js:2115 Error: Form responses must redirect to another location
at FormSubmission.requestSucceededWithResponse (turbo.es2017-esm.js:679:27)
at FetchRequest.receive (turbo.es2017-esm.js:450:27)
at FetchRequest.perform (turbo.es2017-esm.js:431:31)

It is because all clicks on links and form submissions are now TURBO_STREAM requests in rails 7,
For getting faster response and also we do not need to write explicit code to make the TURBO_STREAM request.

What TURBO_STREAM request does ?

It’s generally update turbo frame on page without loading the whole page.

To Resolve this issue

we need to handle the TURBO_STREAM request in our controller like that

  def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: 'Post was successfully created.'
else
respond_to do |format|
format.turbo_stream { render turbo_stream: turbo_stream.replace(@post, partial: 'posts/form', locals: { post: @post }) }
format.html { render :new }
end
end
end

The turbo_stream.replace method is part of the Turbo Streams library in Rails. It generates a Turbo Stream action that replaces a part of the page without a full page refresh.

Here’s a breakdown of the method and its attributes:

  • format.turbo_stream: This specifies that the following block should be used to respond to Turbo Stream requests. Turbo Stream is a part of the Hotwire framework that allows you to send updates to specific parts of the page over WebSocket.
  • { render turbo_stream: turbo_stream.replace(@post, partial: 'posts/form', locals: { post: @post }) }: This is the block that gets executed for Turbo Stream requests. It's using the render method to send a response.
  • turbo_stream.replace(@post, partial: 'posts/form', locals: { post: @post }): This is a Turbo Stream action that replaces a part of the page. It's using the replace method, which replaces a Turbo Frame or a Turbo Stream element on the page with new content.
  • @post: This is the target of the replace action. It should correspond to the ID of a Turbo Frame or a Turbo Stream element on the page.
  • In out case the target is new_post which is form id , replace method automatically target form id because we have pass @post in the first argument
  • partial: 'posts/form': This specifies the partial to render and replace the target with.
  • locals: { post: @post }: This passes local variables to the partial. In this case, it's passing the @post instance variable as a local variable named post.

Also we will add error on view for the handle failure of form submission.

<h2>Creating an Post</h2>

<%= form_for post, html: { class: 'row' } do |f| %>
<% if post.errors.any? %>
<div class="col-12">
<div class="alert alert-danger">
<ul>
<% post.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
</div>
<% end %>

<div class="col-auto">
<%= f.label :title, class: 'form-label' %>
<%= f.text_field :title, class: 'form-control' %>
</div>
<div class="col-auto">
<%= f.label :body, class: 'form-label' %>
<%= f.text_area :body, class: 'form-control' %>
</div>
<div class="col-auto mt-4">
<%= f.submit class: 'btn btn-primary' %>
</div>
<% end %>

This will resolve our issue to handle failure of form submission in rails 7

Conclusion :-

Turbo is a part of the Hotwire framework introduced by Basecamp. It’s designed to provide a way to build modern web applications with minimal JavaScript by sending HTML over the wire. Turbo consists of three main parts: Turbo Drive, Turbo Frames, and Turbo Streams.

By following above steps, you’ve successfully implemented Turbo Stream to handle form submissions and display errors gracefully in a Rails 7 application. This approach enhances the user experience by providing immediate feedback without requiring a full page reload, leveraging the power of Hotwire’s Turbo library.

--

--

Kanhaiya Dubey
0 Followers

Seasoned Ruby on Rails dev with 10+ years in full stack development. Passionate about clean code, agile methods, and delivering exceptional web applications.