Remote Data with Courier
Problem
You want to use Sourcerer to manage pagination, search, and loading state for a remote list, while also using Courier for typed API clients, authorization headers, retry logic, and request interceptors. Using both independently would require manually coordinating request lifecycles.
Solution
Pass the Courier API client call directly inside createRemoteSource()'s fetch callback. The AbortSignal provided by Sourcerer should be forwarded to Courier to enable automatic request cancellation.
import { createApi } from '@vielzeug/courier';
import { createRemoteSource } from '@vielzeug/sourcerer';
type Issue = { id: number; title: string; status: 'open' | 'closed' };
type IssueFilter = { status?: 'open' | 'closed' };
type IssueSort = { by: 'title' | 'id'; dir: 'asc' | 'desc' };
// Configure your Courier API client once
const api = createApi({
baseUrl: '/api',
headers: () => ({ Authorization: `Bearer ${getToken()}` }),
});
// Use it inside the fetch callback
const source = createRemoteSource<Issue, IssueFilter, IssueSort>({
fetch: async ({ filter, limit, page, search, sort }, signal) =>
api.get('/issues', {
query: { filter: JSON.stringify(filter), limit, page, q: search, sort: JSON.stringify(sort) },
signal,
}),
filter: { status: 'open' },
sort: { by: 'title', dir: 'asc' },
limit: 25,
});
await source.ready();Why pass signal?
Sourcerer manages request concurrency automatically. When a new request supersedes an in-flight one, Sourcerer aborts it via the provided AbortSignal. Forwarding it to Courier ensures the underlying HTTP request is actually cancelled at the network level — saving bandwidth and preventing stale responses from settling after the state has moved on.
Optimistic updates with Courier
// Optimistically close an issue in the UI before the server confirms
const rollback = source.optimisticUpdate((issues) =>
issues.map((i) => (i.id === targetId ? { ...i, status: 'closed' } : i)),
);
try {
await api.patch(`/issues/${targetId}`, { body: { status: 'closed' } });
await source.refresh(); // server confirmed — optimistic state cleared
} catch {
rollback(); // server rejected — restore previous state
}Pitfalls
- The
signalparameter is positional — it is the second argument of thefetchcallback, after the query object. Do not forget to include it in the function signature. - Courier requests that are aborted via
signalthrow aDOMExceptionwith name'AbortError'. Sourcerer swallows aborted-request errors automatically — you do not need to catch them insidefetch. - Keep the Courier client instance outside the
fetchcallback so it is created once, not on every request.