StunkStunk
Async

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 reload states out of the box — no manual state juggling needed.

import { asyncChunk } from "stunk";

const userChunk = asyncChunk(async () => {
  const res = await fetch("/api/user");
  return res.json();
});

The chunk fetches immediately on creation and keeps its state reactive. Subscribe to it or use it in React with useAsyncChunk.


State shape

Every async chunk exposes these reactive properties:

PropertyTypeDescription
dataT | nullThe resolved value, or null before first fetch
loadingbooleantrue while a fetch is in progress
errorE | nullThe error if the last fetch failed, otherwise null
reload() => Promise<void>Manually trigger a refetch

API reference

reload(params?)

Forces a fresh fetch, ignoring stale time. Optionally pass new params.

await postsChunk.reload();
await postsChunk.reload({ category: "engineering" });

refresh(params?)

Smart refresh — only fetches if data is stale (respects staleTime). Does nothing if data is still fresh.

await postsChunk.refresh();

mutate(mutator)

Update data directly without triggering a network request. Useful for optimistic updates.

postsChunk.mutate((current) => [...(current ?? []), newPost]);

setParams(params)

Update the fetcher params and immediately trigger a fresh fetch.

postsChunk.setParams({ category: "engineering" });

reset()

Resets back to initial state and re-fetches from scratch.

postsChunk.reset();

cleanup()

Clears any active polling intervals and cache timers. Call this when you're done with the chunk.

postsChunk.cleanup();

import { asyncChunk } from "stunk";

const postsChunk = asyncChunk(async () => {
  const res = await fetch("/api/posts");
  return res.json() as Promise<Post[]>;
});

// Read current state
const { data, loading, error } = postsChunk.get();

// Subscribe to changes
postsChunk.subscribe(({ data, loading, error }) => {
  console.log({ data, loading, error });
});

// Refetch manually
await postsChunk.reload();

With parameters

Pass parameters to the fetcher using the params option:

const postChunk = asyncChunk(async ({ id }: { id: string }) => {
  const res = await fetch(`/api/posts/${id}`);
  return res.json() as Promise<Post>;
});

// Set params and trigger a fetch
postChunk.setParams({ id: "42" });

Retry on failure

Configure automatic retries with retryCount and retryDelay:

const dataChunk = asyncChunk(fetcher, {
  retryCount: 3, // retry up to 3 times
  retryDelay: 1000, // wait 1s between retries
});

Stale & cache time

Control how long data is considered fresh and how long it stays cached:

const dataChunk = asyncChunk(fetcher, {
  staleTime: 30_000, // data is fresh for 30s — won't refetch
  cacheTime: 60_000, // cache is kept for 60s after last subscriber leaves
});

Error handling

Use the onError callback to react to failures:

const dataChunk = asyncChunk(fetcher, {
  onError: (error) => {
    console.error("Fetch failed:", error.message);
  },
});

Or read error directly from state:

const { error } = dataChunk.get();
if (error) {
  console.error(error.message);
}

In React

Use useAsyncChunk to consume async state reactively in a component:

import { asyncChunk } from "stunk";
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>
  );
}

useAsyncChunk automatically triggers the initial fetch and re-renders the component whenever loading, data, or error changes.


What's next?

On this page