Hi! I’m creating an infinitescroll and the current behaviour is: when I scroll at the very bottom, existing elements are replaced instead of adding it at the bottom. I’m using scrivener_ecto for pagination, following the liveview docs and threads on how to build it.
created live component :
def render(assign) do
...
<div class="grid grid-cols-3 auto-rows-[32rem] gap-2">
<%= for image <- @images do %>
<div id={"image-#{image.slug}"}>
<a href={Routes.image_path(@socket, :show, image.slug)}>
<img class="object-cover h-full" src={image.url} />
</a>
<a
class="absolute bottom-4 left-4 flex gap-2 items-center"
href={Routes.user_profile_path(@socket, :show, image.user.username)}
>
<img
class="rounded-full w-9 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-900"
src={Accounts.avatar_url(@socket, image.user.avatar)}
/>
<span><%= image.user.username %></span>
</a>
</div>
<% end %>
</div>
<div id="infinite-scroll" phx-hook="InfiniteScroll" phx-update="append" data-page={@page}></div>
...
Then added in a template:
# live/gallery_live/index.html.heex
...
<section>
<.live_component
module={GalleryComponent}
id="gallery"
images={@images}
page={@page}/>
</section>
created live_view
# live/gallery_live/index.ex
@impl true
def mount(_params, _session, socket) do
{:ok,
socket
|> assign(page: 1)
|> assign(per_page: 10)
|> fetch(), temporary_assigns: [images: []]}
end
@impl true
def handle_event("load-more", _unsigned_params, %{assigns: assigns} = socket) do
{:noreply, assign(socket, page: assigns.page + 1) |> fetch()}
end
defp fetch(%{assigns: %{page: page, per_page: per_page}} = socket) do
images = Something.paginate_images(page: page, per_page: per_page)
assign(socket, images: images)
end
js file for observing scroll events
// assets/js/infinite_scroll.js
export const InfiniteScroll = {
page() {return this.el.dataset.page;},
loadMore(entries) {
const target = entries[0];
if (target.isIntersecting && this.pending == this.page()) {
this.pending = this.page() + 1;
this.pushEvent("load-more", {});
}
},
mounted() {
this.pending = this.page();
this.observer = new IntersectionObserver(
(entries) => this.loadMore(entries),
{
root: null, // window by default
rootMargin: "0px",
threshold: 1.0,
}
);
this.observer.observe(this.el);
},
beforeDestroy() {
this.observer.unobserve(this.el);
},
updated() {
this.pending = this.page();
},
};
added hooks
// assets/js/app.js
...
import {InfiniteScroll} from "./infinite_scroll"
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {hooks: {InfiniteScroll}, params: {_csrf_token: csrfToken}})
...
Am I missing something? I’m not sure why child elements were replaced instead of adding them down.