Dynamically re-use & lazy-load page using Hotwire
Tags: hotwire, rails, ruby, admin
Published: 2022-03-01 15:31:11

---
title: Dynamically re-use & lazy-load page using Hotwire
published: true
description: How we use the power of Hotwire with Avo to lazy-load the common pages.
tags: hotwire, rails, ruby, admin
cover_image: https://d2g0xdrrde9ln1.cloudfront.net/yikp804vb8kwg3ung6xmnlpy2ces
---

_This article was originally published on [avo.cool](https://avohq.io/blog/dynamically-re-use-pages-with-hotwire)_

Hotwire is a fantastic technology that helps you build dynamic websites without thinking about JavaScript.

When we re-wrote Avo from VueJS to Hotwire, when it first came out, we had to think about how we could leverage it to our advantage. 

One of the first things we did was to add dynamic turbo-frames around common pages.

For example, the `ResourceIndex` page is the page that usually displays the table with the requested resources (it shows the users on `/users`).
We knew that we could use that exact partial when we wanted to display the `has_many` association on the `ResourceShow` page but didn't know how at first. We could have extracted it to a partial and rendered it in the `ResourceShow` page underneath the record details, but the `ResourceShow` page would take a long time to load when you have many associations to one record.

We then came up with the idea to use a turbo-frame for that but still didn't want to use a partial.

Let's say we have `User` and `Team` models like so:

``` ruby
class Team < ApplicationRecord
  has_many :users
end

class User < ApplicationRecord
  belongs_to :team
end
```

The routes and controllers look like this:

``` ruby
resources :posts

get "/:resource_name/:id/:related_name/", to: "associations#index"
```

``` ruby
class BaseController < ApplicationController
  def index
    # if there's a query set up, use it, if not, set one up
    unless defined? @query
      @query = @resource.class.query_scope
    end
  end
end

class AssociationsController < BaseController
  def index
    # find the parent record and set the query
    @query = @parent_model.public_send(params[:related_name])
  end
end
```

So what happens there? When a user goes to `/users`, they will see the list of users, and if they go to `/teams/TEAM_ID/users`, they should see the same list of users but scoped to the respective team.

In order to achieve this using `turbo-frame`s we'll add a lazy-loaded turbo frame on the `RecordShow` page with the `src` attribute set to the association path with the `?turbo_frame="has_many_users"` param added to the path `/teams/TEAM_ID/users?turbo_frame="has_many_users"` url.

``` erb
<!-- views/base/show.html.erb -->

<!-- record details here -->

<!-- association details below -->
<turbo-frame id="has_many_users" src="/teams/#{@team.id}/users?turbo_frame=has_many_users" target="_top">
  <!-- Loading state -->
</turbo-frame>
```

Next, we should add a dynamic wrap around the `index.html.erb` partial like so.

``` erb
<!-- views/base/index.html.erb -->

<% if params[:turbo_frame].present? %>
  <turbo-frame id="<%= params[:turbo_frame] %>">
<% end %>

  <!-- The list of records -->

<% if params[:turbo_frame].present? %>
  </turbo-frame>
<% end %>
``` 

What happens is that when the user loads a teams `Show` page `/teams/1`, that page will display with the lazy-loaded frame (so no impact on the performance of that page on load), which in turn, loads the association `Index` page with the `turbo_frame` param. That will add the `<turbo-frame id="has_many_users">` tag around the template allowing Turbo to replace the content dynamically on the page.

We're re-using the actual `index.html.erb` template and the `BaseController#index` action.

Of course, this can be improved using some helpers and a partial.

``` ruby
# app/helpers/application_helper.rb

def turbo_frame_wrap(name, &block)
  render layout: "partials/turbo_frame_wrap", locals: {name: name} do
    capture(&block)
  end
end
```

``` erb
<!-- app/views/partials/turbo_frame_wrap.html.erb -->

<% if name.present? %><turbo-frame id="<%= name %>"><% end %>
  <%= yield %>
<% if name.present? %></turbo-frame><% end %>
```

``` erb
<!-- views/base/show.html.erb -->

<!-- record details here -->

<!-- association details below -->
<turbo-frame id="<%= turbo_frame %>" src="<%= frame_url %>" target="_top">
  <!-- Loading state -->
</turbo-frame>
```

``` erb
<!-- views/base/index.html.erb -->

<%= turbo_frame_wrap(params[:turbo_frame]) do %>
  <!-- The list of records -->
<% end %>
```

You can do the same thing for `Show` pages too. We did with Avo.

You can find a more detailed example on Avo's GitHub repo.

 - [`index.html.erb`](https://github.com/avo-hq/avo/blob/main/app/views/avo/base/index.html.erb)
 - [`turbo_frame_wrap`](https://github.com/avo-hq/avo/blob/main/app/helpers/avo/application_helper.rb#L23)
 - [`turbo_frame_wrapper_component.html.erb`](https://github.com/avo-hq/avo/blob/main/app/components/avo/turbo_frame_wrapper_component.html.erb)

Stay cool and improve performance 💪

[avo.cool/](https://avo.cool/)
[avo.cool/repo](https://avo.cool/repo)
[avo.cool/feedback](https://avo.cool/feedback)
[avo.cool/try](https://avo.cool/try)