Pagination
Loading paginated data with infinite scroll
Pagination Example
This example shows how to use createPagedStache to implement an infinite scroll pattern with paginated data.
Live Demo
Showing 0 of 0 posts
Loading posts...
The Code
Query Definition
Create a paged stache for fetching posts:
import { createPagedStache } from 'svelte-stache';
interface Post {
id: string;
title: string;
excerpt: string;
author: string;
category: string;
publishedAt: string;
readTime: number;
}
interface PostsResponse {
data: Post[];
meta: {
total: number;
page: number;
perPage: number;
totalPages: number;
};
}
interface PostFilters {
category: string | null;
}
export const { useStache: getPosts, invalidate: invalidatePosts } = createPagedStache({
id: 'posts',
pageSize: 10,
fetch: async (filters: PostFilters, { pageIndex, pageSize }): Promise<PostsResponse> => {
const params = new URLSearchParams({
page: String(pageIndex),
limit: String(pageSize)
});
if (filters.category && filters.category !== 'all') {
params.set('category', filters.category);
}
const response = await fetch(`/api/posts?${params}`);
return response.json();
},
getData: (response) => response.data,
getTotalCount: (response) => response.meta.total,
staleTime: 1000 * 60 * 2 // 2 minutes
});
Component Usage
<script lang="ts">
import { getPosts } from '$lib/queries/posts';
let category = $state<string>('all');
let pageCount = $state(1);
// Reset page count when category changes
$effect(() => {
category; // Track category changes
pageCount = 1;
});
const posts = getPosts(() => ({
params: { category: category === 'all' ? null : category },
pageParams: {
pageOffset: 0,
pageCount
}
}));
const loadMore = () => {
if (posts.hasMore && !posts.isLoading) {
pageCount++;
}
};
</script>
<div class="filters">
<label for="category">Category:</label>
<select id="category" bind:value={category}>
<option value="all">All Categories</option>
<option value="technology">Technology</option>
<option value="design">Design</option>
<option value="business">Business</option>
<option value="lifestyle">Lifestyle</option>
<option value="science">Science</option>
</select>
</div>
<div class="stats">
Showing {posts.data?.length ?? 0} of {posts.totalCount} posts
</div>
<div class="posts-grid">
{#each posts.data ?? [] as post}
<article class="post-card">
<span class="category">{post.category}</span>
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
<div class="post-meta">
<span>{post.author}</span>
<span>{post.readTime} min read</span>
</div>
</article>
{/each}
</div>
{#if posts.isLoading}
<div class="loading">Loading more posts...</div>
{/if}
{#if posts.hasMore && !posts.isLoading}
<button class="load-more" onclick={loadMore}> Load More Posts </button>
{/if}
{#if !posts.hasMore && posts.data?.length}
<p class="end-message">You've reached the end!</p>
{/if}
Key Points
- Page Aggregation - All loaded pages are automatically combined into a single
dataarray - Total Count - Use
getTotalCountto extract the total from your API response - Has More - The
hasMoreproperty tells you if there are more pages to load - Filter Reset - When filters change, reset
pageCountto start fresh - Loading States -
isLoadingis true when loading any page
What to Try
- Click "Load More" - Loads the next page and appends to the list
- Change category - Resets to page 1 and fetches filtered results
- Load multiple pages, then change category - Everything resets cleanly
- Watch the stats - Shows how many posts are loaded vs total