DEV Community

Vlad Radulescu
Vlad Radulescu

Posted on

Infinite Scrolling with Rails's Turbo/Hotwire

Before we begin, I need to acknowledge that this is not a full proof solution. There's a lot of features lacking.
However, my goal was to achieve a basic infinite scrolling using only Turbo, without any JavaScript or Stimulus.

You can view it Live at: https://games.directory/u/pacmakaveli/c/library

For this, I'm using Rails, Pagy, TailwindCSS, and, Turbo Rails.

Implementation is quite simple, knowing that Turbo's turbo_frame_tag(loading: :lazy) is only loaded when the element is visible in the DOM.


  def library
    @pagy, @collections = pagy(user.collections, items: 18, page: params[:page])

    if params[:page]
      render(turbo_stream: turbo_stream.append('user:collections:library', partial: 'user/collections/collections.library'))

      # Ensure we return, otherwise it will complain about double rendering!
      #
      return
    end

    render(template: 'user/collections/index.library')
  end
Enter fullscreen mode Exit fullscreen mode

Because I'm not using Rails's naming convention when it comes to partials, I have to manually declare the render.
In most apps this won't probably be needed.

Next up is the views:

This is my index.library.slim view:

.cell-auto
  .column
    = turbo_frame_tag('user:collections:library') do
      = render(partial: 'collections.library')
Enter fullscreen mode Exit fullscreen mode

And this is the _collections.library.slim partial:

.grid.grid-cols-6 class='gap-0.5'

  - @collections.each do |collection|

    = turbo_frame_tag(collection, class: 'col-span-1') do
      = link_to(user_collection_url(user, collection), class: 'column card relative', target: :_top) do
        = lazy_image_tag(collection.cover, class: 'w-full h-60 rounded-md object-cover') if collection.cover.attached?

        = collection.name unless collection.cover.attached?

        .bg-main.rounded.bg-opacity-50.w-10.h-10.absolute.z-10 class='bottom-1 left-1 p-1.5'
          = image_tag("logos/#{ collection.network.class.name.split('::').first.downcase }", class: 'w-full h-full')

= turbo_frame_tag(@pagy.next, loading: :lazy, src: library_user_collections_path(page: @pagy.next)) if @pagy.next
Enter fullscreen mode Exit fullscreen mode

The magic is on the last line, where turbo_frame_tag will load the next set of results when it becomes visible in the DOM.

And that's it! Turbo is quite powerful, in my opinion, when it comes to stuff like this.

In the near future I want to explore this and introduce a couple of new features, like the pagination on the page reducing the number of pages as it loads, the URL adding the page number on each load and, probably the most important one, removing previous entries from the DOM as the page keeps loading more entries.
It tends to become really slow when you have too many library items loaded on the page since the Images are high quality.

Let me know what you think!

:)

Discussion (0)