Multiple Turbo Streams
Making multiple targeted frame updates using turbo stream.
hotwire
turbo drive
turbo frame
turbo stream
turbo
Updating Multiple Frames with a Single Action in Turbo Stream: A Simple Solution
While working on a website using Ruby on Rails with Hotwire, I encountered a tricky issue. I needed to update two Turbo frames with a single action, triggered by a user click. After spending hours trying to solve it, I eventually realized the solution was much simpler than I thought. Here's a breakdown of the problem and how I resolved it.
Scenario
In my application, a visitor can create a post as a guest, and there's a special flow for guest users:
- Guest Login Link: A link allows the user to log in as a guest.
- Three Actions on Click:
- A new session is created for the guest (handled by the session controller).
- The login icon updates to a logout icon (using Turbo Stream).
- The main content area is replaced with a new post form (again using Turbo Stream).
The challenge I faced was that Turbo Stream updates only one frame at a time. If I tried to update both the login icon and the main content simultaneously, one of them would fail to update properly. The page would update only partially — either the icon updated, or the content frame updated, but never both.
I spent a lot of time debugging and trying to come up with a solution. Initially, I resorted to a workaround by forcing a full page reload through a Turbo Stream response. However, this was not an ideal solution. It bypassed the benefits of Hotwire by refreshing the entire page instead of making targeted updates.
I spent a lot of time debugging and trying to come up with a solution. Initially, I resorted to a workaround by forcing a full page reload through a Turbo Stream response. However, this was not an ideal solution. It bypassed the benefits of Hotwire by refreshing the entire page instead of making targeted updates.
Initial Solution (Not Ideal)
# Guest Login Action
def guest_login
if guest_user = User.authenticate_by(email_address: "guest@guest.com", password: "121212")
start_new_session_for guest_user
respond_to do |format|
format.html { redirect_to after_authentication_url }
format.turbo_stream do
# Send a Turbo Stream that triggers a full-page reload
render turbo_stream: turbo_stream.append("main_content_frame", "<script>window.location.reload();</script>")
end
end
else
redirect_to root_path, alert: "Guest login failed. Please try again later."
end
end
endThe Real Solution: Use Multiple Turbo Streams in One Response
The real solution lies in using multiple Turbo Streams in a single response. Turbo allows us to update multiple frames simultaneously, as long as we structure the response correctly. Rather than using JavaScript to force a page reload, we can send two separate Turbo Streams to update the login icon and the main content frame.
Here’s how I refactored the solution:
# Guest Login Action
def guest_login
if guest_user = User.authenticate_by(email_address: "guest@guest.com", password: "121212")
start_new_session_for guest_user
@post = Post.new
respond_to do |format|
format.html { redirect_to after_authentication_url }
format.turbo_stream do
# Render a Turbo Stream response that updates both frames
render turbo_stream: [
turbo_stream.replace("main_content_frame", template: "posts/new"),
turbo_stream.replace("login_buttons", partial: "sessions/login_buttons")
]
end
end
else
redirect_to root_path, alert: "Guest login failed. Please try again later."
end
endTo achieve this, I had to wrap the login component in a separate Turbo Frame so that both frames could be updated simultaneously:
<div class="logins">
<button class="btn btn-primary"><%= image_tag("search.svg") %></button>
<%= turbo_frame_tag "login_buttons_frame" do %>
<%= render "sessions/login_buttons" %>
<% end %>
</div>Rails Hotwire enables us to create responsive websites with minimal JavaScript, allowing for a seamless user interface and clean, reusable code.
This was just a quick post for site testing with dynamic content updates.