Infinite Auto Scroll using Turbo only ( No Stimulus / No Javascript )


In this article, we will create a Rails 7 Application and implement Infininte Auto Scroll. The scroll will be done using Hotwire Turbo only, and not a single line of Javascript will be used.

The App will display ten posts by default and on reaching the end of the page, more posts in batches of ten will be introduced further until there are no more posts.

Create a Rails 7 Application

We get started by creating a Rails 7 Application.

rails _7.0.3_ new infinite-scroll-turbo

Add a Model

We are adding a simple model with only a body attribute, to keep things simple.

rails g scaffold Post body:text
rails db:migrate

Set the Posts page as the Home Page

Change the default root to Posts#index in config\routes.rb

root "posts#index"

Add Simple CSS

Adding simple CSS makes the application more presentable.

<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css">

Adding a Turbo Form to create some Post Entries

We will add the Post Form partial created by the Scaffold command to the index page.

<div id="new_post">
<%= render partial: "posts/form", locals: { post: Post.new } %>
</div>

This makes it more convenient to add more Posts

Adding a Turbo Frame to list Posts through a Turbo Stream

We add the following turbo_frame to app/views/posts/index.html.erb

<div id="posts">
  <%= turbo_frame_tag "posts", src: posts_path(format: :turbo_stream), loading: :lazy %>
</div>

and the corresponding Index code for turbo in a file app/views/posts/index.turbo_stream.erb


<%= turbo_stream.append "posts" do %>
  <%= render partial: "posts/post", collection: @posts %>
<% end %>


Instead of using index.turbo_stream.erb, the same can be achieved by adding a respond_to format in turbo_stream method.

Posts turbo frame and Posts#index turbe stream illustrate the magic of Turbo / Rails7. What we are doing is dynamically loading content from Posts#index page, as the DOM encounters the Posts turbo_frame without writing any JavaScript code.

Adding Pagination

To get the load more functioning, we need to implement pagination functionality. We can change the Posts#index to the following

  def index
    page_limit = 10
    @current_page = params[:page].to_i

    @posts = Post.offset(page_limit*@current_page).limit(page_limit)
    @next_page = @current_page + 1 if(Post.all.count > page_limit*@current_page + page_limit)
  end

The above code will return @posts based on the params[:page] in groups of 10, which can be set by page_limit

Additionally, @next_page and @current_page variables are set to indicate if there is a next page available, and the value of the current page.

Dynamically loading Posts with TurboFrames

As illustrated in the previous section we can call a get method through a turbo stream using Turbo_frame, we will use this approach to append pages to a frame until there are no more posts.

We can change the app/posts/index.html.erb code to the following code


<div id="posts">
</div>

<%= turbo_frame_tag "load_more", src: posts_path(format: :turbo_stream), loading: :lazy %>

and app/posts/index.turbo_stream.erb as follows:

 <%= turbo_stream.append "posts" do %>
  <%= render partial: "posts/post", collection: @posts %>
<% end %>

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

We have an empty div#posts and a frame after that which recursively l azy loads more posts until there are none.

As soon as the end of the turbo_frame_tag#load_more appears, it will call the index.turbo_stream which will append the paginated posts to div#posts and replace the div#load_more with a fresh turbo_frame_tag of the @next_page

Conclusion

Turbo extends the Rails Frameworks to make Responsive Applications with minimal code.

Join Our Newsletter