Reputation: 66242
On a new vanilla Rails 7 project, I'm trying to create a simple view with a form that, when submitted, renders the same view with some additional information.
Here's my controller:
class MyController < ApplicationController
def index
puts "got index request"
@message = "type something into the form"
end
def create
puts "got create request"
@message = "hello world"
render :index
end
end
Here's index.html.erb
:
<p style="color: green"><%= notice %></p>
<h1>New search</h1>
<%= render "form" %>
<p>Message is <%= @message.inspect %>.</p>
I expected the view to say Message is "hello world".
since the instance variable should've been overwritten. However, it still says Message is "type something into the form".
.
I tried 3 variants of render
and got the same result:
render :index
render :index, message: @message
render :index, locals: {message: @message}
I can see from the server logs that the create
method is getting called on submit (it prints got create request
) and it is rendering the index view (i see index.html.erb
in the log); it's just that the instance variable isn't getting passed through.
Why isn't the instance variable passed to the view? Is something getting cached? What should I do instead?
Upvotes: 1
Views: 561
Reputation: 29821
While it's technically explained here:
https://turbo.hotwired.dev/handbook/drive#redirecting-after-a-form-submission
Sometimes it's hard to connect all the dots in Turbo.
# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
def index
@message = "type something into the form"
end
def create
@message = params[:form_message]
render :index
end
end
# app/views/messages/index.html.erb
<%= form_with url: "/messages" do |f| %>
<%= f.text_field :form_message %>
<%= f.submit %>
<% end %>
<div id="message_id"><%= @message %></div>
If you submit this form you get this in the browser console:
turbo.es2017-esm.js:2348 Error: Form responses must redirect to another location
at O.requestSucceededWithResponse (turbo.es2017-esm.js:780:27)
at I.receive (turbo.es2017-esm.js:543:27)
at I.perform (turbo.es2017-esm.js:518:31)
After submitting a form, you have to redirect or render an error response, default is status 422
aka :unprocessable_entity
:
render :index, status: :unprocessable_entity
Because forms are submitted as TURBO_STREAM format, you can render turbo_stream response:
respond_to do |format|
format.html { render :index, status: 422 }
format.turbo_stream do
render turbo_stream: turbo_stream.update(:message_id, params[:form_message])
end # ^ helper^ action^ ^id ^content
end # response format
This is probably the preferred way.
Put it in a frame. Not as efficient as turbo_stream, because it renders the whole page on the server, but on the front end effect is the same, only message is updated:
<%= form_with url: "/messages", data: { turbo_frame: :message_id } do |f| %>
<%= f.text_field :form_message %>
<%= f.submit %>
<% end %>
<%= turbo_frame_tag :message_id do %>
<%= @message %>
<% end %>
Just render :index
works fine.
Send GET request instead:
# app/views/messages/index.html.erb
<%= form_with url: "/messages", method: :get do |f| %>
...
# app/controllers/messages_controller.rb
def index
@message = params[:form_message] || "type something into the form"
end
Because the server is actually rendering a response and sending it back, we can override turbo rendering logic and just render the response instead of complaining:
// app/javascript/application.js
document.addEventListener("turbo:before-fetch-response", async (event) => {
if (event.target.hasAttribute("data-render-post")) {
event.preventDefault()
const response = new DOMParser().parseFromString(await event.detail.fetchResponse.responseHTML, "text/html")
// you're free, do what you like here
document.body.replaceWith(response.body)
}
})
# app/views/messages/index.html.erb
# i imagine you don't want to do that for every form
# vvvvvvvvvvvvvvvvv
<%= form_with url: "/messages", data: { render_post: true } do |f| %>
...
The usual redirect then render will work. You can also have turbo_stream response in redirected action. Or turbo_frame_tag in the template if form is targeting a turbo_frame:
def create
redirect_to message_path(1, form_message: params[:form_message])
end
def show
@message = params[:form_message]
respond_to do |format|
format.html
format.turbo_stream { render turbo_stream: turbo_stream.update(:message_id, @message) }
end
end
And it behaves the same as described above, there is just an extra redirect in between.
Upvotes: 2