createPagedStache
API reference for creating paginated query definitions
createPagedStache
Creates a paginated query definition that manages multiple pages of data. Each page is a separate cached instance, enabling per-page caching and independent status tracking.
Import
import { createPagedStache } from 'svelte-stache';
Signature
function createPagedStache<Params, PageResult, PageFetchReturn>(
options: CreatePagedStacheOptions<Params, PageResult, PageFetchReturn>
): {
useStache: UsePagedStacheFn<Params, PageResult>;
invalidate: (params: Params) => void;
};
Options
Inherits all options from createStache, plus:
Required Options
pageSize
Type: number
The number of items per page. This is passed to your fetch function.
createPagedStache({
id: 'posts',
pageSize: 20,
fetch: fetchPosts
});
fetch
Type: (params: Params, pageParams: { pageIndex: number; pageSize: number }) => Promise<PageFetchReturn> | PageFetchReturn
A function that fetches a single page. Receives both your params and pagination info.
createPagedStache({
id: 'posts',
pageSize: 20,
fetch: async (filters, { pageIndex, pageSize }) => {
const res = await fetch(
`/api/posts?page=${pageIndex}&size=${pageSize}&category=${filters.category}`
);
return res.json();
}
});
Optional Options
getData
Type: (response: PageFetchReturn) => PageResult
Extracts the page data from the API response. Use when your API wraps data in an object.
createPagedStache({
id: 'posts',
pageSize: 20,
fetch: fetchPosts,
// API returns { items: [...], total: 100 }
getData: (response) => response.items
});
getTotalCount
Type: (response: PageFetchReturn) => number
Extracts the total count from the API response. Used to calculate hasMore. If not provided, falls back to pages.length * pageSize.
createPagedStache({
id: 'posts',
pageSize: 20,
fetch: fetchPosts,
getData: (response) => response.items,
getTotalCount: (response) => response.total
});
Return Value
useStache
A function to use the paginated query in a component.
invalidate
A function to manually invalidate all cached pages for specific params.
useStache Options
When calling useStache, you pass pagination params in addition to regular params:
const posts = getPosts(() => ({
params: { category: 'tech' },
pageParams: {
pageOffset: 0,
pageCount: 3
}
}));
params
Type: Params
Your custom parameters passed to the fetch function.
pageParams
Type: { pageOffset: number; pageCount: number }
Controls which pages to fetch:
pageOffset- The starting page index (0-based, default: 0)pageCount- How many consecutive pages to fetch (default: 1)
// Fetch pages 0, 1, 2
pageParams: { pageOffset: 0, pageCount: 3 }
// Fetch pages 5, 6
pageParams: { pageOffset: 5, pageCount: 2 }
enabled
Type: boolean Default: true
Same as createStache - controls whether fetching is enabled.
Paged Stache Object
Returns all base stache properties, plus pagination-specific ones:
Pagination Properties
| Property | Type | Description |
|---|---|---|
data | PageResult | null | All loaded items aggregated (flattened via .flat()) |
pages | Array<PageResult | null> | Per-page data after getData(), null if not yet loaded |
totalCount | number | Total items (from getTotalCount or pages.length * pageSize) |
hasMore | boolean | Whether more pages exist (data.length < totalCount) |
Status Aggregation
Status properties are aggregated across all loaded pages:
isLoading- True if any page is loadingisError- True if any page has an errorisSuccess- True only if all pages succeededcacheStatus-'fresh'if all pages fresh,'stale'if any stale,'empty'otherwise
Examples
Basic Pagination
const { useStache: getPosts } = createPagedStache({
id: 'posts',
pageSize: 10,
fetch: async (_, { pageIndex, pageSize }) => {
const res = await fetch(`/api/posts?page=${pageIndex}&limit=${pageSize}`);
return res.json();
}
});
With API Response Parsing
interface ApiResponse {
data: Post[];
meta: {
total: number;
page: number;
perPage: number;
};
}
const { useStache: getPosts } = createPagedStache({
id: 'posts',
pageSize: 20,
fetch: async (filters, { pageIndex, pageSize }): Promise<ApiResponse> => {
const res = await fetch(
`/api/posts?page=${pageIndex}&limit=${pageSize}&status=${filters.status}`
);
return res.json();
},
getData: (response) => response.data,
getTotalCount: (response) => response.meta.total
});
Infinite Scroll Component
<script lang="ts">
import { getPosts } from '$lib/queries/posts';
let pageCount = $state(1);
const posts = getPosts(() => ({
params: null,
pageParams: { pageOffset: 0, pageCount }
}));
const loadMore = () => {
if (posts.hasMore && !posts.isLoading) {
pageCount++;
}
};
</script>
<div class="posts">
{#each posts.data ?? [] as post}
<article>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
{/each}
</div>
{#if posts.isLoading}
<div class="loading">Loading...</div>
{/if}
{#if posts.hasMore && !posts.isLoading}
<button onclick={loadMore}>Load More</button>
{/if}
{#if !posts.hasMore && posts.data?.length}
<p>No more posts</p>
{/if}
With Filters
<script lang="ts">
import { getPosts } from '$lib/queries/posts';
let category = $state('all');
let pageCount = $state(1);
// Reset page count when category changes
$effect(() => {
category; // Track category
pageCount = 1;
});
const posts = getPosts(() => ({
params: { category },
pageParams: { pageOffset: 0, pageCount }
}));
</script>
<select bind:value={category}>
<option value="all">All Categories</option>
<option value="tech">Technology</option>
<option value="design">Design</option>
</select>
<div class="posts">
{#each posts.data ?? [] as post}
<article>{post.title}</article>
{/each}
</div>
{#if posts.hasMore}
<button onclick={() => pageCount++}>Load More</button>
{/if}
Page Window (Virtualized)
Load a "window" of pages around the current view:
<script lang="ts">
import { getPosts } from '$lib/queries/posts';
let currentPage = $state(0);
const windowSize = 3; // Load 3 pages around current
const posts = getPosts(() => ({
params: null,
pageParams: {
pageOffset: Math.max(0, currentPage - 1),
pageCount: windowSize
}
}));
</script>
<button onclick={() => (currentPage = Math.max(0, currentPage - 1))}> Previous </button>
<span>Page {currentPage + 1}</span>
<button onclick={() => currentPage++} disabled={!posts.hasMore}> Next </button>