Infinite Scroll with data-query
Append the next page to the query's rows; row identity keeps the list stable while it grows.
The plain Infinite Scroll pattern pushes fetched items onto an array in component state and guards against double loads by hand.
With a data-query (Full build), the next page is refresh({ append: true }): the query accumulates the rows, dedupes them by key, and marks itself isStale while the page is in flight.
Live Demo
The demo runs in its own frame on the Full build. Open it on its own ↗
Source
HTML + JavaScript
<div data-component="feed-view">
<div class="feed" data-action="scroll:checkScroll">
<div data-list="items" data-key="id">
<template><div class="item" data-bind="title"></div></template>
</div>
</div>
<span data-bind="$feed.count"></span> loaded
<span data-show="$feed.isStale">Loading more…</span>
<button data-action="loadMore" data-bind-attr="{ disabled: !hasMore }">Load more</button>
</div>
<script>
// The page number lives in a store so every request the query makes
// reads the same value: the source reads it, the component advances it.
wildflower.store('pager', { state: { page: 1 } });
wildflower.query('feed', {
from: '/api/feed',
key: 'id',
params: () => ({ page: wildflower.getStore('pager').page })
});
wildflower.component('feed-view', {
state: { hasMore: true, busy: false },
computed: {
items() { return wildflower.getQuery('feed').rows; }
},
checkScroll(event, element) {
if (element.scrollTop + element.clientHeight >= element.scrollHeight - 24) this.loadMore();
},
async loadMore() {
if (this.busy || !this.hasMore) return;
this.busy = true;
wildflower.getStore('pager').page++;
await wildflower.getQuery('feed').refresh({ append: true }); // accumulate, don't swap
this.hasMore = wildflower.getQuery('feed').count < 48;
this.busy = false;
}
});
</script>
Key Points
refresh({ append: true })adds the new page to the rows already on screen instead of replacing them;appendneeds akey, and a row that arrives twice updates in place rather than duplicating- The page number belongs in a store the source reads (here through
params), so a later focus refresh or poll asks for the page the user is looking at rather than snapping back to page one - After the first append, background refreshes merge: new rows land at the head, existing rows update in place, and the user keeps their place; a bare
refresh()is the reset that starts over isStaleis the "loading more" indicator andcountthe loaded total;hasMoreand the scroll sentinel stay in your component, since the query ships the data primitive and not the widget- Needs the Full build; the plain pattern keeps the array in component state. Numbered pages, deletes in a windowed fetch and ordering are in Paged and Infinite Results