InfiniteScrollReflex
Successively add HTML fragments to the DOM on demand
How?
- CableReady is used to insert a new set of items using
insert_adjacent_html
before a#sentinel
element after a “Load more” button is clicked. - A scoped page morph is used to determine the next
page
and hides the “Load more” button when the last page is reached.
Caveat
Note that in a real-world app, you’d probably want to use model partials and collection rendering instead of inline rendering the items.
Variations
- Use a Stimulus controller and an
IntersectionObserver
to automatically trigger loading:
import ApplicationController from "./application_controller";
import { useIntersection } from "stimulus-use";
export default class extends ApplicationController {
static targets = ["button"];
connect() {
super.connect();
useIntersection(this, { element: this.buttonTarget });
}
appear() {
this.buttonTarget.disabled = true;
this.buttonTarget.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
this.stimulate("ArticlesInfiniteScroll#load_more", this.buttonTarget);
}
}
- This example uses Pagy for pagination, but of course you could also just use
.limit
and.offset
or any other pagination method.
app/reflexes/infinite_scroll_reflex.rb
class InfiniteScrollReflex < ApplicationReflex
include Pagy::Backend
attr_reader :collection
def load_more
cable_ready.insert_adjacent_html(
selector: selector,
html: render collection,
position: position
)
end
def page
element.dataset.next_page
end
def position
"beforebegin"
end
def selector
raise NotImplementedError
end
end
app/reflexes/articles_infinite_scroll_reflex.rb
class ArticlesInfiniteScrollReflex < InfiniteScrollReflex
def load_more
@pagy, @collection = pagy Article.all, page: page
super
end
def selector
"#sentinel"
end
end
app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def index
@pagy, @collection = pagy Article.all unless @stimulus_reflex
end
end
app/models/article.rb
class Article < ApplicationRecord
has_one_attached :image
end
app/views/articles/index.html.erb
<ul>
<%= render @collection %>
<li id="sentinel" class="hidden"></li>
</ul>
<div id="load-more">
<% if @pagy.page < @pagy.last %>
<button
data-reflex="click->InfiniteScroll#load_more"
data-next-page="<%= @pagy.page + 1 %>"
data-reflex-root="#load-more">
Load more
</button>
<% end %>
</div>
app/views/articles/_article.html.erb
<li>
<%= image_tag article.image %>
</li>