Load More
The button sends the current $offset; the endpoint answers with three SSE
events: patchElements (mode append) adds the next batch to #entries,
patchSignals advances the offset, and — once everything is loaded —
removeElements('#load-more-button') takes the button away. ?offset=
deep-links the already-loaded state.
Walks (23)
- Canal towpath loopA 3 km route, roughly 45 minutes at an easy pace.
- Castle mound circuitA 10 km route, roughly 58 minutes at an easy pace.
- Old orchard trailA 8 km route, roughly 71 minutes at an easy pace.
- River meadow walkA 6 km route, roughly 84 minutes at an easy pace.
- Ironstone ridge pathA 4 km route, roughly 97 minutes at an easy pace.
The demo
---
/** Load-more demo. Deep link: ?offset=… server-renders what's loaded. */
import EntryBatch from '../EntryBatch.astro';
import { entries } from '../../data/entries';
const LIMIT = 5;
const raw = Number(Astro.url.searchParams.get('offset')) || LIMIT;
const offset = Math.min(
Math.max(LIMIT, Math.ceil(raw / LIMIT) * LIMIT),
Math.ceil(entries.length / LIMIT) * LIMIT,
);
const done = offset >= entries.length;
---
<section
class="demo"
data-signals={`{offset: ${offset}}`}
data-replace-url={`'/examples/load-more' + ($offset > ${LIMIT} ? '?offset=' + $offset : '')`}
>
<h2>Walks ({entries.length})</h2>
<ul id="entries" class="entry-list">
<EntryBatch offset={0} limit={offset} />
</ul>
{
!done && (
<div id="load-more-button">
<button
data-on:click="@get('/api/load-more')"
data-indicator="_loading"
data-attr:disabled="$_loading"
>
Load more
</button>
</div>
)
}
</section>The shared component
---
/** One batch of entries; /api/load-more appends later batches to #entries. */
import { entries } from '../data/entries';
interface Props {
offset: number;
limit?: number;
}
const { offset, limit = 5 } = Astro.props;
const items = entries.slice(offset, offset + limit);
---
{
items.map((entry) => (
<li>
<strong>{entry.title}</strong>
<span class="hint">{entry.blurb}</span>
</li>
))
}The endpoint
import {
patchElements,
patchSignals,
readSignals,
removeElements,
SignalsValidationError,
sse,
} from '@wrux/astro-datastar/server';
import type { APIRoute } from 'astro';
import { experimental_AstroContainer as AstroContainer } from 'astro/container';
import { z } from 'astro/zod';
import EntryBatch from '../../components/EntryBatch.astro';
import { entries } from '../../data/entries';
export const prerender = false;
const LIMIT = 5;
const signals = z.object({
offset: z.number().int().min(0).catch(0),
});
export const GET: APIRoute = async ({ request }) => {
let offset: number;
try {
({ offset } = await readSignals(request, signals));
} catch (err) {
if (err instanceof SignalsValidationError) return err.response();
throw err;
}
const container = await AstroContainer.create();
const batch = await container.renderToString(EntryBatch, {
props: { offset, limit: LIMIT },
});
const nextOffset = offset + LIMIT;
const events = [
patchElements(batch, { selector: '#entries', mode: 'append' }),
patchSignals({ offset: nextOffset }),
];
if (nextOffset >= entries.length) {
events.push(removeElements('#load-more-button'));
}
return sse(...events);
};