Infinite Scroll with Rails and Turbo - No JavaScript Required!
Tags: rails, ruby, tutorial
Published: 2024-03-05 19:40:46

---
title: Infinite Scroll with Rails and Turbo - No JavaScript Required!
published: true
date: 2024-02-26 21:18:46 UTC
tags: rails, ruby, tutorial
canonical_url: https://webcrunch.com/posts/infinite-scroll-rails-turbo
---

{% youtube F63GgJSYE9I %}

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

```bash
rails new infinite_scroll_turbo -j esbuild
```

### 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](https://railsui.com).

```bash
bundle add tailwindcss-rails faker will_paginate
rails tailwindcss:install
```

### Generate some records

First, I’ll set up the development database and house some dummy content we’ll generate with the [faker](https://github.com/faker-ruby/faker) gem we just installed.

```bash
rails generate scaffold Post title:string content:text
rails db:migrate
```

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

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

Finally, run the seed command to generate those records.

```bash
rails db:seed
```

### Update Rails routing

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

```ruby
# 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
```

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](https://github.com/mislav/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.

```ruby
# app/controllers/posts_controller.rb 

def index
  @posts = Post.paginate(page: params[:page], per_page: 4)
end
```

### 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.

```erb
<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>
```

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:

```erb
<%= 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 %>
```

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`.

```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>
```

## 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

- [Digging into View Transitions with Turbo 8 and Rails](https://dev.to/justalever/digging-into-view-transitions-with-turbo-8-and-rails-3plk-temp-slug-1484289)
- [Digging into Turbo 8's Morphing Feature in Ruby on Rails](https://webcrunch.com/posts/turbo-morphing-ruby-on-rails)
- [How to Add Live Reload to Your Rails Application](https://dev.to/justalever/how-to-add-live-reload-to-your-rails-application-36id-temp-slug-3994798)