Async Chunk
Handle async operations with built-in loading, error, and data states.
asyncChunk() wraps an async fetcher function and gives you reactive loading, error, data, and lastFetched states out of the box — no manual state juggling needed.
import { asyncChunk } from "stunk/query";
const userChunk = asyncChunk(async () => {
const res = await fetch("/api/user");
return res.json();
});Fetchers with no parameters are called automatically on creation. Fetchers that take parameters wait for setParams() or reload().
State shape
| Property | Type | Description |
|---|---|---|
data | T | null | The resolved value, or null before first fetch |
loading | boolean | true while a fetch is in progress |
error | E | null | The error if the last fetch failed, otherwise null |
lastFetched | number | undefined | Timestamp of the last successful fetch |
isPlaceholderData | boolean | true when showing previous data while a new fetch is in progress |
Options
| Option | Type | Default | Description |
|---|---|---|---|
key | string | auto | Deduplication key — concurrent calls share one request |
enabled | boolean | ((params: Partial<P>) => boolean) | true | Disable fetching until ready — receives current params |
initialData | T | null | Seed data before the first fetch |
keepPreviousData | boolean | false | Show previous data while refetching — no UI flicker |
onSuccess | (data: T) => void | — | Called after every successful fetch |
onError | (error: E) => void | — | Called when all retries are exhausted |
retryCount | number | 0 | Number of retries on failure |
retryDelay | number | 1000 | ms between retries |
staleTime | number | 0 | ms before data is considered stale |
cacheTime | number | 300_000 | ms to keep cache after last subscriber leaves |
refetchInterval | number | — | Auto-refetch interval in ms |
refetchOnWindowFocus | boolean | false | Refetch when window regains focus |
pagination | object | — | Enable pagination — see Pagination section below |
All options except key, enabled, initialData, keepPreviousData, and
pagination can be set globally via configureQuery() — per-chunk options
always override global defaults.
Conditional fetching with enabled
enabled accepts a boolean or a function that receives the current params. This is the cleanest way to prevent fetching until required data is available:
// Static — never fetches
const chunk = asyncChunk(fetcher, { enabled: false });
// Dynamic — re-evaluated on every setParams call
const flatsByHouseChunk = asyncChunk(
({ houseId }: { houseId: string }) => apiGetFlatsByHouse(houseId),
{ enabled: ({ houseId }) => !!houseId },
);The function receives the same params the fetcher will get — defined once on the chunk, no repetition at the hook callsite.
API reference
reload(params?)
Forces a fresh fetch, ignoring stale time.
await postsChunk.reload();
await postsChunk.reload({ category: "engineering" });refresh(params?)
Smart refresh — only fetches if data is stale. Does nothing if data is still fresh.
await postsChunk.refresh();mutate(mutator)
Update data directly without a network request. Useful for optimistic updates.
postsChunk.mutate((current) => [...(current ?? []), newPost]);setParams(params)
Update fetcher params and trigger a fresh fetch. Pass null for a key to remove it.
postsChunk.setParams({ category: "engineering" });
postsChunk.setParams({ category: null }); // removes category keyclearParams()
Wipes all current params and refetches.
postsChunk.clearParams();reset()
Resets to initial state and re-fetches from scratch.
postsChunk.reset();cleanup()
Safe cleanup — only tears down intervals and listeners if no active subscribers remain.
postsChunk.cleanup();forceCleanup()
Tears down all side effects regardless of active subscribers.
postsChunk.forceCleanup();Request deduplication
Use key to deduplicate concurrent requests — if two components call reload() simultaneously on a chunk with the same key, only one request fires:
const userChunk = asyncChunk(fetchUser, { key: "user" });
userChunk.reload();
userChunk.reload(); // joins the same in-flight requestkeepPreviousData
When params change, the default behavior shows null while loading. Set keepPreviousData: true to keep showing the previous data instead — no flash of empty state:
const postsChunk = asyncChunk(
({ category }: { category: string }) => fetchPosts(category),
{ keepPreviousData: true },
);
// While loading, isPlaceholderData is true and data still shows previous results
postsChunk.setParams({ category: "engineering" });In React
import { asyncChunk } from "stunk/query";
import { useAsyncChunk } from "stunk/react";
const postsChunk = asyncChunk(async () => {
const res = await fetch("/api/posts");
return res.json() as Promise<Post[]>;
});
function PostList() {
const { data, loading, error, reload } = useAsyncChunk(postsChunk);
if (loading) return <p>Loading...</p>;
if (error)
return (
<p>
Error: {error.message} <button onClick={reload}>Retry</button>
</p>
);
return (
<ul>
{data?.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}