Menu
+

Multiple Turbo Streams
Making multiple targeted frame updates using turbo stream.
hotwire
turbo drive
turbo frame
turbo stream
turbo
David Gim
Wednesday, 22 January 25




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:

  1. Guest Login Link: A link allows the user to log in as a guest.
  2. 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.

 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
end


The 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
  end

To 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.