Rails 7.0.4, issue with Turbo: Submitting a form loads the response below the body tag. I have added local: true

Rails 7.0.4

This form, when submitted, loads the response below the body tag. When I remove the turbo script on the layout page, the form works as expected, and loads the full page. How can I make it work while having turbo enabled?

The form:

= form_with( url: signin_path, method: :post, local: true) do |f|

  .form--user
    .field
      = f.label :email
      = f.email_field :email
    .field
      = f.label :password
      = f.password_field :password
    .field
      = f.submit "Login"

The controller:

class UserSessionsController < ApplicationController

  def create
    @user = login(params[:email], params[:password])
    if @user
      redirect_to user_home_path, notice: "Signed in!"
    else
      flash.now[:alert] = 'Login failed - please provide correct credentials.'
      render action: 'new'
    end
  end

  def destroy
    logout
    redirect_to(:signin, notice: 'Signed out!')
  end

end

On successful login, the content of the page the browser should be redirected to loads below the body tag:

On a failed login, nothing happens (the flash message does not show - unless turbo is disabled).

The scripts in the layout file:

<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_include_tag "application", "data-turbo-track": "reload", defer: true %>

Here’s the log from a successful login:

Started POST "/sign-in" for 127.0.0.1 at 2022-12-25 23:08:09 -0800
Processing by UserSessionsController#create as TURBO_STREAM
  Parameters: {"authenticity_token"=>"[FILTERED]", "email"=>"user1@gmail.com", "password"=>"[FILTERED]", "commit"=>"Login"}
  User Load (1.1ms)  SELECT "users".* FROM "users" WHERE "users"."email" = 'user1@gmail.com' ORDER BY "users"."id" ASC LIMIT $1  [["LIMIT", 1]]
  ↳ app/controllers/user_sessions_controller.rb:4:in `create'
Redirected to http://lvh.me:3000/
Completed 302 Found in 136ms (ActiveRecord: 1.1ms | Allocations: 1135)


Started GET "/" for 127.0.0.1 at 2022-12-25 23:08:09 -0800
Processing by HomeController#show as TURBO_STREAM
  User Load (0.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 14], ["LIMIT", 1]]
  Rendering home/show.slim
  Rendered home/show.slim (Duration: 0.1ms | Allocations: 98)
Completed 200 OK in 2ms (Views: 0.6ms | ActiveRecord: 0.5ms | Allocations: 2620)

How can I make this work?

I am seeing two things:

  1. The form needs a data attribute telling it the ID of the turbo frame to target with results,
  2. The element (div?) which should display the result of the form submission needs to be wrapped in a turbo frame with the ID that the submitting form is passed using the data hash.

Hotwire (Turbo Drive, Turbo Frames, Turbo Streams and Stimulus) works by intercepting HTTP requests and performing asynchronous requests to the server via the WebSocket protocol. It does this without posting the entire document. It then receives responses with which to update targeted DOM elements.

If you would like a comprehensive understanding of Hotwire and how to tie everything together, I highly recommend this Hotwire for Rails Developers course by Mike & Nicole Clark at The Pragmatic Studio.

I hereby attach an example of a search form which searches fish baits and targets an identified div wrapped in a turbo frame using the turbo_frame_tag helper.

Response copied from browser’s network panel:

<div class="search">
  <form data-turbo-frame="results" data-turbo-action="advance" data-controller="form" data-action="input-&gt;form#submit" action="/baits" accept-charset="UTF-8" method="get">
    <div>
      <input placeholder="Name..." value="" type="search" name="name" id="name" />
    </div>
    <div>
      <select name="category" id="category"><option value="">Any Category</option>
<option value="Crankbait">Crankbait</option>
<option value="Softbait">Softbait</option>
<option selected="selected" value="Spinner">Spinner</option>
<option value="Popper">Popper</option>
<option value="Fly">Fly</option>
<option value="Spoon">Spoon</option></select>
    </div>
    <div>
      <select name="sort" id="sort"><option value="">Sort By</option>
<option selected="selected" value="name">By Name</option>
<option value="tackle_box_items_count">By Popularity</option></select>
    </div>
</form></div>

<turbo-frame id="results">
	<div class="baits">
	  <div id="bait_8">
	<div class="bait">
		00:42
	  <a href="/baits/8"><img src="/assets/bucktail-090a107d0192b9b81ec81f55e0d4a090f3109b04704c86fdd5a8284176417140.png" /></a>
	  <div class="name">
	    <a href="/baits/8">Bucktail</a>
	  </div>
	  <div class="category">
	    Spinner
	  </div>
	  <div class="popularity">
	    2
	  </div>
	      <form class="button_to" method="post" action="/tackle_box_items?bait_id=8"><button type="submit">Add</button><input type="hidden" name="authenticity_token" value="RkVuhV79wlVCgb0WkVhgt5sKCttktU2H_Yu_EepzqVMlWAFLUEidQ_e4XAd7d_KTiX02gGTVLWd2fY8Z6t3G2A" autocomplete="off" /></form>
	</div>
</div><div id="bait_3">
	<div class="bait">
		00:42
	  <a href="/baits/3"><img src="/assets/fire-tiger-b1865e08c7ae397e4e59240311b544fc99eab3562a44fe464807e5c2c0b09782.png" /></a>
	  <div class="name">
	    <a href="/baits/3">Fire Tiger</a>
	  </div>
	  <div class="category">
	    Spinner
	  </div>
	  <div class="popularity">
	    6
	  </div>
	      <form class="button_to" method="post" action="/tackle_box_items/41"><input type="hidden" name="_method" value="delete" autocomplete="off" /><button type="submit">Remove</button><input type="hidden" name="authenticity_token" value="qgTtCRxfS0i7rPrY9gQsacKwaulhao_CRl5TAr-tAXvT99a9BoJT7P41CzwT8K2PcSiPugukfStoWIl7p2PXzQ" autocomplete="off" /></form>
	</div>
</div><div id="bait_12">
	<div class="bait">
		00:42
	  <a href="/baits/12"><img src="/assets/purple-reign-1a238210839a4777e84c0ab290c6adcda52b6f9ca838b2514df3a7e919e8eed4.png" /></a>
	  <div class="name">
	    <a href="/baits/12">Purple Reign</a>
	  </div>
	  <div class="category">
	    Spinner
	  </div>
	  <div class="popularity">
	    4
	  </div>
	      <form class="button_to" method="post" action="/tackle_box_items?bait_id=12"><button type="submit">Add</button><input type="hidden" name="authenticity_token" value="u3obnSMOo2MSmQY54VK3S1KQwnd7q3B1H1E3Q8uFXazYZ3RTLbv8daeg5ygLfSVvQOf-LHvLEJWUpwdLyysyJw" autocomplete="off" /></form>
	</div>
</div><div id="bait_6">
	<div class="bait">
		00:42
	  <a href="/baits/6"><img src="/assets/rooster-tail-585978dca4b203bdb1e2b34d419fa992a91369c15d3bbc84e84944cb63492766.png" /></a>
	  <div class="name">
	    <a href="/baits/6">Rooster Tail</a>
	  </div>
	  <div class="category">
	    Spinner
	  </div>
	  <div class="popularity">
	    3
	  </div>
	      <form class="button_to" method="post" action="/tackle_box_items?bait_id=6"><button type="submit">Add</button><input type="hidden" name="authenticity_token" value="NbLU3bMwtDkV1StGPKInBJ5YSZj2g4iHZeS-y8C2CqJWr7sTvYXrL6DsylfWjbUgjC91w_bj6GfuEo7DwBhlKQ" autocomplete="off" /></form>
	</div>
</div>
	</div>
</turbo-frame>