ruby-on-railsstimulusjshotwire

Reload turboframe with Hotwire (Stimulus/Turbo/Rails)


I'm working with Hotwire the last month or two and trying to understand how to reload a specific turbo-frame after it renders the first time.

Let's say I have a simple rails controller for user searches. If it has a param of name, return users matching that name, if not, return all users.

class UserSearchController
  def show
    return User.where(name: params[:name]) if params[:name].present?

    return User.all
  end
end

I have a Turboframe hooked up that is correctly lazy loading all users in the DOM. What I'm trying to figure out is how I can update that Turboframe and redraw the frame. I've played around a bit with Stimulus and trying to set the src to the endpoint, that's the suggestions I've seen in other posts:

  reload() {
    const {src: src} = this;
    this.src = null;
    this.src = src;
  }

That doesn't seem to work though, it doesn't redraw the turboframe. I can see it's making the request though to the rails backend.

Would anyone be able to point me in the direction of reloading/redrawing a frame after a page loads? I'm not sure if I'm completely off or am in the right ballpark.


Solution

  • You need some instance variables in that show action or render locals: {.., otherwise, you return into nothing. In stimulus controller, this refers to controller instance, without seeing the rest of it, it looks like it does nothing.

    Copy this to any page and click away to reload:

    <%= turbo_frame_tag :reloadable, style: "display:none;" do %>
      <%= request.uuid %>
    <% end %>
    
    # clicking this frame will load that ^ frame, because it's first.
    <%= turbo_frame_tag :reloadable, src: request.url, onclick: "reload()" %>
    

    Duplicate ids, I know. Put it on separate pages, not duplicate anymore.


    Because onclick isn't a cool thing to do:

    // app/javascript/controllers/hello_controller.js
    
    import { Controller } from "@hotwired/stimulus";
    
    export default class extends Controller {
      reload() {
        this.element.reload();
    
        // this works as well
        // this.element.src = this.element.src;
    
        // not sure if you would ever need to do it this way
        // this.element.removeAttribute("complete");
      }
    }
    
    <%= turbo_frame_tag(:reloadable, style: "display:none;") { request.uuid } %>
    
    <%= turbo_frame_tag :reloadable, src: request.url,
      data: {
        controller: "hello",
        action: "click->hello#reload"
      }
    %>
    

    https://turbo.hotwired.dev/reference/frames#functions


    As for the search form:

    # app/views/any/where.html.erb
    
    <%= form_with url: "/users", method: :get,
      data: {turbo_frame: :users} do |f| %>
    
      <%= f.search_field :name, placeholder: "search by name" %>
      <%= f.button "Search" %>
    <% end %>
    
    # load this frame from `src`. load it again from the form submission response.
    <%= turbo_frame_tag :users, src: "/users", target: :_top %>
    
    # app/views/users/index.html.erb
    
    <%
      scope = User.all
      scope = scope.where(name: params[:name]) if params[:name].present?
      @users = scope
    %>
    
    <%= turbo_frame_tag :users, target: :_top do %>
      <% @users.each do |user| %>
        <%= user.name %>
      <% end %>
    <% end %>
    

    https://turbo.hotwired.dev/handbook/frames#targeting-navigation-into-or-out-of-a-frame