DEV Community

Andy Leverenz
Andy Leverenz

Posted on • Originally published at webcrunch.com on

Infinite Scroll with Rails and Turbo - No JavaScript Required!

In this blog post, I will walk you through creating an infinite scroll feature using Rails and Turbo. I wanted to see if using only Turbo was an option for this feature, and it turns out it is. I was excited to discover this, so I wanted to share the wealth.

I will be sure to cover the necessary steps, including setting up the database and handling the server-side logic. By the end of this guide, you will have a fully functional infinite scroll feature in your Rails application that you can borrow on your own.

Important: Make sure you’re using the latest version of Rails and turbo-rails gem.

Create a new app

rails new infinite_scroll_turbo -j esbuild
Enter fullscreen mode Exit fullscreen mode

Add Tailwind CSS, will_paginate, and faker gems

To make this demo more realistic, I’ll use the tailwindcss-rails gem for some ready-to-go styles out of the box.

If you'd like more control consider passing -c tailwind in your rails new command which gives you a blank slate OR if you want some UI done for you check out my project Rails UI.

bundle add tailwindcss-rails faker will_paginate
rails tailwindcss:install
Enter fullscreen mode Exit fullscreen mode

Generate some records

First, I’ll set up the development database and house some dummy content we’ll generate with the faker gem we just installed.

rails generate scaffold Post title:string content:text
rails db:migrate
Enter fullscreen mode Exit fullscreen mode

Add some Ruby code to seed data more quickly inside db/seeds.rb

# db/seeds.rb
50.times do
  Post.create(title: Faker::Lorem.sentence(word_count: 4), content: Faker::Lorem.paragraph(sentence_count: 4))
end
Enter fullscreen mode Exit fullscreen mode

Finally, run the seed command to generate those records.

rails db:seed
Enter fullscreen mode Exit fullscreen mode

Update Rails routing

Since we scaffolded a Post model, I'll root the app to posts#index

# config/routes.rb
Rails.application.routes.draw do
  resources :posts
  get "up" => "rails/health#show", as: :rails_health_check
  root "posts#index" # uncomment this line!
end
Enter fullscreen mode Exit fullscreen mode

With our database and seeded data in the app, we’re ready to consider the controller logic.

To make infinite scrolling work, we’ll need some form of pagination.

I reached for will_paginate because it’s super simple to set up and use. There are many more options for pagination with Rails, so feel free to swap for something you prefer.

Update the posts controller

In the PostsController, I added the following code.

# app/controllers/posts_controller.rb 

def index
  @posts = Post.paginate(page: params[:page], per_page: 4)
end
Enter fullscreen mode Exit fullscreen mode

Infinite scroll logic with Rails and Turbo

To use no JavaScript we’ll use a unique way to render a index.turbo_stream.erb file in the form of a turbo stream response. Here's the code in my posts/index.html.erb file.

<div class="w-full">
  <% if notice.present? %>
    <p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-lg inline-block" id="notice"><%= notice %></p>
  <% end %>

  <div class="flex justify-between items-center">
    <h1 class="font-bold text-4xl">Posts</h1>
    <%= link_to "New post", new_post_path, class: "rounded-lg py-3 px-5 bg-blue-600 text-white block font-medium" %>
  </div>

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

  <% if @posts.next_page %>
    <%= turbo_frame_tag "load_more", src: posts_path(page: @posts.next_page, format: :turbo_stream), loading: :lazy %>
  <% end %>
</div>

<div class="fixed bottom-0 rounded-tl right-0 h-12 bg-gray-50/50 backdrop-blur-sm w-full text-lg font-medium py-2 text-gray-900 text-center border-t border-l border-gray-200/80 w-56">
  <p>🚀 Current page: <span id="current_page"><%= @posts.current_page %></span></p>
</div>
Enter fullscreen mode Exit fullscreen mode

Using the turbo_frame_tag we can source a defined path in the Rails app to load lazily. This just works which is impressive in itself.

Real-time infinite pagination

I passed an explicit format to the path helpers (i.e. posts_path(format: :turbo_stream) ).

Doing this instructs the rails app to return index.turbo_stream.erb view logic instead of the default index.html.erb file using the advertised “HTML over the wire” approach.

Create a new file called index.turbo_stream.erb in your app/views/posts folder. Inside the index.turbo_stream.erb file, I added the following:

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

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

<%= turbo_stream.update "current_page", @posts.current_page %>
Enter fullscreen mode Exit fullscreen mode

Using will_paginate, we can conditionally check for a next_page based on the parameters set in the controller.

If a new page is present, I display the turbo_frame_tag “load_more” in the index.html.erb and index.turbo_stream.erb views.

This dynamically loads via the paths passed to the src argument on the turbo_frame_tag.

Notice all formats are loaded as :turbo_stream. This continues happening recursively until no pages are left as you scroll.

To make each post more resemble a blog post, I updated the markup and styling slightly inside _post.html.erb.

<article id="<%= dom_id post %>" class="py-6">

  <h1 class="text-2xl font-semibold tracking-tight"><%= post.title %></h1>

  <p class="my-5 text-lg text-gray-700">
    <%= post.content %>
  </p>

  <% if action_name != "show" %>
    <%= link_to "Show this post", post, class: "rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium" %>
    <%= link_to "Edit this post", edit_post_path(post), class: "rounded-lg py-3 ml-2 px-5 bg-gray-100 inline-block font-medium" %>
    <hr class="mt-6">
  <% end %>
</article>
Enter fullscreen mode Exit fullscreen mode

Done!

And with that, we have infinite scroll using Ruby on Rails and Turbo 8. No additional JavaScript/Stimulus.js is necessary. I think this is a game changer and I hope this tip helps you code your Rails app a little faster.

Keep reading

Top comments (0)