Infinite Scroll#
Load more items as the user scrolls.
See also
Official Inertia.js docs: Infinite Scroll
What Is Infinite Scroll?#
Infinite scroll combines partial reloads with merge props so new data is appended to the existing list rather than replacing it.
Backend Usage#
Mark list props as mergeable and provide scroll metadata:
from litestar import get
from litestar.params import FromQuery
from litestar_vite.inertia import merge, scroll_props, InertiaResponse
@get("/posts", component="Posts")
async def list_posts(page: FromQuery[int] = 1) -> InertiaResponse:
posts = await Post.paginate(page=page, per_page=20)
return InertiaResponse(
{"posts": merge("posts", posts.items, match_on="id")},
scroll_props=scroll_props(
page_name="page",
current_page=page,
previous_page=page - 1 if page > 1 else None,
next_page=page + 1 if posts.has_next else None,
),
)
Automatic Scroll Props#
You can also return a pagination container and enable infinite_scroll on
the route. Litestar-Vite will extract items and scroll props for you:
from litestar.params import FromQuery
from litestar.pagination import OffsetPagination
@get("/posts", component="Posts", infinite_scroll=True, key="posts")
async def list_posts(offset: FromQuery[int] = 0, limit: FromQuery[int] = 20) -> OffsetPagination[Post]:
posts, total = await Post.paginate(offset=offset, limit=limit)
return OffsetPagination(items=posts, total=total, limit=limit, offset=offset)
Frontend Usage#
Trigger partial reloads when the bottom sentinel becomes visible:
import { usePage, WhenVisible } from "@inertiajs/react";
export default function Posts() {
const page = usePage();
const { posts } = page.props;
const postScroll = page.props.scrollProps?.posts;
return (
<div>
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
{postScroll?.nextPage && (
<WhenVisible
always
params={{ only: ["posts"], data: { page: postScroll.nextPage } }}
>
<Spinner />
</WhenVisible>
)}
</div>
);
}
<script setup lang="ts">
import { usePage, WhenVisible } from "@inertiajs/vue3";
const page = usePage();
const { posts } = page.props;
</script>
<template>
<div>
<PostCard v-for="post in posts" :key="post.id" :post="post" />
<WhenVisible
v-if="page.props.scrollProps?.posts?.nextPage"
always
:params="{ only: ['posts'], data: { page: page.props.scrollProps.posts.nextPage } }"
>
<Spinner />
</WhenVisible>
</div>
</template>
Tips#
Use
match_oninmerge()to avoid duplicates when refreshing.Use
page_nameto isolate multiple scroll regions on one page.Combine with Remembering State to preserve filters across visits.
See Also#
Merging Props - Merge strategies and match_on
Load When Visible - WhenVisible patterns
Partial Reloads - Partial reload mechanics