On error, the server redirects back to the form with errors. Frames will replace the bounding HTML. All good till now
However, after success, I want to move to a different page all together and that page cannot have the same frame in it’s HTML.
A great example of this is a login page. After login, I want to redirect the user to the dashboard vs keeping them on the same login page with a success message.
So is it possible to use some Content-type or some header to instruct turbo that this time just replace the entire page?
Gotcha. How about this? This worked for me where a POST request (made out of a frame) that responded with a redirect would make turbo navigate to the redirect URL.
Turbo Drive handles form submissions in a manner similar to link clicks. The key difference is that form submissions can issue stateful requests using the HTTP POST method, while link clicks only ever issue stateless HTTP GET requests.
After a stateful request from a form submission, Turbo Drive expects the server to return an HTTP 303 redirect response, which it will then follow and use to navigate and update the page without reloading.
Yup. It does work with 303 if the form is out of the frame. I am trying to combine the both. Having the grace of frames to show the validation errors without re-rendering the whole page. But at the same time break out of a frame when the form submission is successful.
I understand that maybe the frames are not designed to handle this use case. But I am curious to know how others are handling situations like this
<%= turbo_frame_tag "new_discussion_message", target: "_top" do %>
<%= form_with model: [@discussion, @message], class: 'new-discussion__form push-double--top d-flex flex-column' do |f| %>
<%= f.hidden_field :creator_id %>
<%= f.rich_text_area :content, data: { controller: "signatured-input", signatured_input_signature: Current.user.signature.to_s } %>
<footer class="new-discussion__footer">
<p class="push--bottom" style="font-size: 1.8rem;">
<%= image_tag @discussion.parent.client.avatar_url %>
<%= @discussion.parent.client.name %> will receive your message in their email.
</p>
<%= f.button class: 'btn btn-primary btn--with-icon', type: 'submit', data: { "disable_with": "Sending…" } do %>
Reply
<% end %>
</footer>
<% end %>
<% end %>
def create
@message = @discussion.messages.new(message_params)
respond_to do |format|
if @message.save
format.any(:html, :js) { redirect_to @discussion.parent.client, flash: { follow_up_with_new_task: true } }
end
end
end
This, however, doesn’t cover the case where @message.save fails the validations. Though you could send a turbo_stream on validation failure to render the errors in your frame.
I haven’t played much with Turbo yet but couldn’t you handle this scenario with Turbo Stream instead of Turbo Frame? You just display the login error message with a Turbo Stream and redirect with Turbo Drive if the login is successful.
The pattern of re-rendering a form with validation errors and redirecting on success is very common - is there a reason Turbo Drive prevents this? What is the simplest workaround here?
See also: https://github.com/hotwired/turbo/issues/22. This seems to be quite a serious breaking bug, especially as there is no easy way to disable Turbo in forms (data-turbo=“false” does not work with links, and the Turbo API does not appear to allow insertion of HTML as with the old Turbolinks.controller.cache.put method).
Either the JS will raise if the frame has a target _top and the backend does not redirect, either the frame has no target but nothing will happen on redirect.
Try adding data-turbo-frame="_top" to your form(not the frame). I tried this and it works.
If you’re using rails form builder: form_with(..., data: {'turbo-frame' => '_top'})
Adding data-turbo-frame="_top" to your form has bad side effect that page jump to top after form submission. Especially visible on long pages on mobile.
now that the new version of turbo is listening to 422 responses, for me it works like this:
I have a standard rails form (not wrapped in a turbo_frame_tag), like so:
= form_with(model: user) do |form|
...
then, a standard rails controller with an action, like so:
def create
@user = User.new(user_params)
if @user.save
redirect_to users_path, status: 303, notice: 'User was successfully created.'
else
render :new, status: :unprocessable_entity
end
end
The important bits are the 303 status code for the redirect, and the status :unprocessable_entity to rerender the form with validation errors.
If you have a setup like this, you don’t need to wrap your forms into a frame. Turbo takes care of rerendering the form with validation errors, and you can redirect to any path you’d like!
So the best solution that I have found was this. Do not use turbo_frame_tag. The redirect process is not working the way it should. Meaning, when you redirect it should redirect to the new page, that’s the intent of that command.
Right Turbo, on redirect, tries to replace a turbo_frame_tag that it came from. And if it doesn’t find it after it processes HTML from redirected page, it fails to “redirect” to the new page. Maybe, the solution should be this: if frame_tag is present, replace it, if not replace the whole page.
For now, here’s the workaround that works best:
Instead of using turbo_frame_tag. create a new.turbo_stream.erb file for your form.
Wrap your form into a div with id. And use turbostream to replace that part of the page.
_form.html.erb
<%= tag.div "new_board_form" do %>
<%= form_with board, url: new_board_path do %>
<% end %>
<% end %>
new.turbo_stream.erb ← to render errors
<%= turbo_stream.replace "new_board_form" do %>
<%= render "form", board: @new_board %>
<% end %>
And then simple redirect on successful form completion
boards_controller.rb
if @new_board.save
redirect_to board_path (@new_board) }
else
render :new
end