I am having a problem with the turbo-rails gem. First I installed the latest version in my Rails 7 application. On my site, I have a select input which is wrapped in a form, with a partial below that form that shows the data. Now I want to apply a filter using the select and dynamically update the data using this turbo-rails package. My form html looks like this:
<div >
<div >
<%= form_with url: '/users/load', method: :get, data: { turbo_frame: :form_response } do |form| %>
<%= render partial: "shared/select", locals: {
placeholder: 'Gender',
width: '90px',
options: @genders,
classes: 'filter',
name: 'gender',
} %>
<%= form.submit %>
<% end %>
</div>
<%= turbo_frame_tag :form_response do %>
<%= render partial: "users/user_list", locals: {
users: @users
} %>
<% end %>
</div>
In my routes, I created this get request which is forwared to a load
method in my controller like this:
get '/users' => "users#index"
get '/users/load' => "users#load"
And then in my controller I have the 2 methods written like this:
class UsersController < ApplicationController
before_action :require_user
USERS_PER_PAGE = 15
def index
@genders = ["Male", "Female"]
@users = User
.limit(USERS_PER_PAGE)
.order(:creation_date).reverse_order
end
def load
@users = User
.limit(USERS_PER_PAGE)
.order(:creation_date).reverse_order
if params[:gender]
@users = @users.where(gender: params[:gender])
end
respond_to do |format|
format.html { render partial: 'users/user_list', locals: { users: @users } }
end
end
end
The problem is that when I go to this page, select a gender and hit the submit button, I get to see the user data with the correct genders, but I only see the partial loaded, so the rest of the page is gone. I can see in the network tab of developer tools in Chrome that the request headers is set to:
text/html,application/xhtml xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
I want to use the turbo-streams instead of the turbo frames, because I need to update more of these items on the same page. Why is it not rendering the content inside the page, instead of rendering the partial only?
How can this be fixed?
CodePudding user response:
To answer your question, you're rendering a partial without turbo stream or turbo frame, so you're only getting a partial as response.
I think, a few examples will explain everything.
# config/routes.rb
resources :users
# app/controllers/users_controller.rb
def index
scope = User.order(created_at: :desc)
scope = scope.where(name: params[:search]) if params[:search]
@users = scope
end
"Preserve log" is quite useful when working with turbo frame and it redirects and clears the console:
https://developer.chrome.com/docs/devtools/console/reference/#persist
Turbo FRAME using GET request with HTML response
We are in index
action and the form is submitting back to index
.
# app/views/users/index.html.erb
# expect :users_index turbo frame in a response vvvvvvvvvvvvvvvvvvvvvvvvv
<%= form_with url: users_path, method: :get, data: { turbo_frame: :users_index } do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
# turbo frame in a response needs to match turbo frame on the current page,
# since we're rendering the same page again, we have the matching frame,
# only content inside this frame is updated.
<%= turbo_frame_tag :users_index do %>
<%= render @users %>
<% end %>
# If you render some other page, you have to wrap it in
# `turbo_frame_tag :users_index`
If you want to update the url as well, so you don't loose the search on refresh:
<%= turbo_frame_tag :users_index, data: { turbo_action: :advance } do %>
<%= render @users %>
<% end %>
Turbo STREAM using GET request with TURBO_STREAM response
You have to set data-turbo-stream="true"
to send a GET stream.
# app/views/users/index.html.erb
<%= form_with url: users_path, method: :get, data: { turbo_stream: true } do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
<%= tag.div id: :users_index do %>
<%= render @users %>
<% end %>
Add turbo_stream
format to respond to this request:
# app/views/users/index.turbo_stream.erb
# update content inside <div id="users_index">
<%= turbo_stream.update :users_index do %>
<%= render @users %>
<% end %>
# add another `turbo_stream` here if you'd like.
Turbo STREAM using POST request with TURBO_STREAM response
# config/routes.rb
resources :users do
# # add `search` action
# post :search, on: :collection
# i'll be lazy and post to :index
post :search, action: :index, on: :collection
end
POST form submissions are sent as TURBO_STREAM by default and it will render index.turbo_stream.erb
.
# app/views/users/index.html.erb
<%= form_with url: search_users_path do |f| %>
<%= f.text_field :search %>
<%= f.submit %>
<% end %>
<%= tag.div id: :users_index do %>
<%= render @users %>
<% end %>
# app/views/users/index.turbo_stream.erb
<%= turbo_stream.update :users_index do %>
<%= render @users %>
<% end %>
Test set up
Just do a simple set up:
rails --version
# Rails 7.0.4
rails new turbo-test -j esbuild
cd turbo-test
bin/rails g scaffold User name
bin/rails db:migrate
open http://localhost:3000/users
bin/dev
# app/controllers/users_controller.rb
def create
@user = User.new(user_params)
respond_to do |format|
if @user.save
# Add this line:
format.turbo_stream { render turbo_stream: turbo_stream.prepend(:users, partial: "user", locals: { user: @user }) }
format.html { redirect_to user_url(@user), notice: "User was successfully created." }
else
format.html { render :new, status: :unprocessable_entity }
end
end
end
# app/views/users/index.html.erb
# submit form
<%= render "form", user: User.new %>
# new user gets prepended here
<div id="users">
<%= render @users %>
</div>