Infinite Scrolling with Hotwire Turbo Frames in Ruby on Rails

In modern web applications, providing a smooth and responsive user experience is paramount. Infinite scrolling is a popular technique that loads content dynamically as the user scrolls, eliminating the need for traditional pagination controls. In this article, we’ll explore how to implement infinite scrolling in a Rails application using Turbo Frames, along with the pagy gem for efficient pagination.

Why Turbo Frames?

Turbo Frames, part of the Hotwire stack, allow you to update only a portion of the page without a full reload. This is particularly useful for infinite scrolling, where you want to append new content to an existing list without disrupting the user’s interaction with the page.

Setting Up Infinite Scrolling with Pagy

To get started, we’ll use the pagy gem, which offers a simple and performant way to handle pagination in Rails. For infinite scrolling, we’ll use the pagy_countless method, ideal when you don’t need to know the total count of items up front.

Here’s how to set up the ProductsController to handle infinite scrolling:

# app/controllers/products_controller.rb

class ProductsController < ApplicationController
include Pagy::Backend

def index
@pagy, @products = pagy_countless(Product.all, items: 25)

respond_to do |format|
format.html
format.turbo_stream
end
end
end

In this code, @pagy manages the pagination logic, while @products holds the items to display. The controller is configured to respond to both HTML and Turbo Stream requests, making it easy to incorporate Turbo Frames.

Building the HTML View

Next, let’s create the HTML view that will display the products and set up Turbo Frames for infinite scrolling:

# app/views/products/index.html.erb

<table>
<thead>
<tr>
<th> Name </th>
</tr>
</thead>
<tbody id="products">
<%= render @products %>
</tbody>
</table>

<% if @pagy.next.present? %>
<%= turbo_frame_tag :next_page, src: products_path(page: @pagy.next, format: :turbo_stream), loading: :lazy %>
<% end %>

Here, the initial set of products is rendered inside a table. If more pages exist, a turbo_frame_tag is added to lazily load the next set of products when the user scrolls near the bottom.

Handling Turbo Stream Updates

When the user reaches the bottom of the page, a new request is made to load additional products. This request is handled by the Turbo Stream view, which appends the new items to the existing list and updates the Turbo Frame for the next page.

# app/views/products/index.turbo_stream.erb

<%= turbo_stream.append :products do %>
<%= render @products %>
<% end %>

<% if @pagy.next.present? %>
<%= turbo_stream.replace :next_page do %>
<%= turbo_frame_tag :next_page, src: products_path(page: @pagy.next, format: :turbo_stream), loading: :lazy %>
<% end %>
<% end %>

Bringing It All Together

With this setup, your Rails application now supports infinite scrolling powered by Turbo Frames. This approach not only improves the user experience by reducing page reloads but also keeps your codebase clean and manageable.

By leveraging the power of Turbo Frames and Pagy, you can implement infinite scrolling in a way that is both performant and easy to maintain. Whether you’re working on a small project or a large-scale application, this technique will help you create a more seamless and modern user interface.