ruby-on-railshotwire-railsturboturbo-frames

Rails Hotwire frame content is being served to the front-end but not replacing the target


I'm simply trying to learn how to use Hotwire with Rails and having trouble with even a basic example.

All I want to do is have a button which refreshes the current time on the index page of my app. BUT! I want the button to be outside of the turbo frame. When the button is inside the turbo frame it works flawlessly, but I want the flexibility of having the button and frame be separate if I need it to be.

The following works fine:

index.html.haml

%h1 The current time is:
= turbo_frame_tag "time_frame" do
  %span= @time
  = button_to "Refresh Time", root_path, method: :get

But this doesn't:

index.html.haml

%h1 The current time is:
= turbo_frame_tag "time_frame" do
  %span= @time

%div
  = button_to "Refresh Time", root_path, method: :get, remote: true, data: { turbo_frame: "time_frame" }

MainController.rb

  def index
    @time = Time.zone.now

    respond_to do |format|
      format.turbo_stream do
        render(turbo_stream: turbo_stream.replace("time_frame", partial: "time_partial"))
      end
      format.html
    end
  end

_time_partial.html.haml

%span= @time

When the button is pressed, I can see that the response comes from the server with the body like so:

<turbo-stream action="replace" target="time_frame"><template><span>2025-07-31 15:43:45 -0700</span>
</template></turbo-stream>

The content-type of the response is "text/vnd.turbo-stream.html; charset=utf-8"

There are not errors in either the frontend or backend.

But the time shown on the page does not change.

What am I doing wrong?


Solution

  • I found the solution. The button needs to be inside its own turbo frame tag in order to function properly. But I can specify the target frame id so that the value of the current time is replaced. Otherwise the frame that has the button will be reloaded (with nothing changed).

    %h1 The current time is:
    = turbo_frame_tag "time_frame" do
      %span= Time.zone.now
    
    = turbo_frame_tag "something_else" do
      = button_to "Refresh Time", root_path, method: :get, data: { turbo_frame: "time_frame" }
    

    With this change, the time updates on button click without refreshing the page. This also works without any of the format code in the controller or the additional partial.

    The response on clicking the button is now text/html instead of text/vnd.turbo-stream.html