StunkStunk
Integrations

React

Official React integration for Stunk — hooks for reading, writing, and subscribing to chunks.

Stunk's React integration is available from stunk/react. It provides hooks that subscribe to chunks and trigger re-renders only when the relevant value changes.

import {
  useChunk,
  useChunkValue,
  useAsyncChunk,
  useInfiniteAsyncChunk,
} from "stunk/react";

useChunk

The primary hook for reading and writing a chunk. Returns [value, set, reset, destroy].

import { chunk } from "stunk";
import { useChunk } from "stunk/react";

const count = chunk(0);

function Counter() {
  const [count, setCount, resetCount] = useChunk(counter);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount((c) => c + 1)}>Increment</button>
      <button onClick={resetCount}>Reset</button>
    </div>
  );
}

With a selector

useChunk accepts an optional selector to subscribe to a slice of the value. The component only re-renders when the selected slice changes:

const user = chunk({ name: "Fola", age: 25, role: "admin" });

function UserName() {
  const [name, setUser] = useChunk(user, (u) => u.name);
  // only re-renders when name changes
}

Return value

const [value, set, reset, destroy] = useChunk(chunk, selector?);
TypeDescription
valueTCurrent chunk value (or selected slice)
set(value | updater) => voidUpdate the chunk
reset() => voidReset to initial value
destroy() => voidDestroy the chunk and clear all subscribers

useChunkValue

Read-only version of useChunk. Use this when a component only needs to read a value — no setter needed.

import { useChunkValue } from "stunk/react";

const theme = chunk<"light" | "dark">("light");

function ThemeDisplay() {
  const currentTheme = useChunkValue(theme);

  return <p>Theme: {currentTheme}</p>;
}

Also accepts a selector:

const user = chunk({ name: "Fola", age: 25 });

function UserAge() {
  const age = useChunkValue(user, (u) => u.age);

  return <p>Age: {age}</p>;
}

This is the preferred hook for derived chunks, computed chunks, and selectors — since those are all read-only:

const price = chunk(100);
const discounted = price.derive((p) => p * 0.9);

function DiscountedPrice() {
  const value = useChunkValue(discounted);

  return <p>Discounted: ${value}</p>;
}

useAsyncChunk

Subscribes to an async chunk and returns its full state with all async methods.

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((p) => (
        <li key={p.id}>{p.title}</li>
      ))}
    </ul>
  );
}

Return value

TypeDescription
dataT | nullResolved data
loadingbooleantrue while fetching
errorE | nullError from last failed fetch
lastFetchednumber | undefinedTimestamp of last successful fetch
reload(params?) => Promise<void>Force refetch
refresh(params?) => Promise<void>Refetch if stale
mutate(mutator) => voidUpdate data directly
reset() => voidReset to initial state
setParams(params) => voidUpdate params and refetch (if chunk accepts params)
paginationPaginationStateCurrent pagination state (if paginated chunk)
nextPage() => Promise<void>Load next page (if paginated chunk)
prevPage() => Promise<void>Load previous page (if paginated chunk)
goToPage(page) => Promise<void>Jump to a page (if paginated chunk)
resetPagination() => Promise<void>Reset to page 1 (if paginated chunk)

useAsyncChunk automatically calls cleanup() when the component unmounts — clearing any polling intervals or cache timers.


useInfiniteAsyncChunk

Wraps useAsyncChunk for infinite scroll. Attach the returned observerTarget ref to a sentinel element at the bottom of your list and the hook handles the rest.

import { infiniteAsyncChunk } from "stunk";
import { useInfiniteAsyncChunk } from "stunk/react";

const postsChunk = infiniteAsyncChunk(
  async ({ page, pageSize }) => {
    const res = await fetch(`/api/posts?page=${page}&limit=${pageSize}`);
    const json = await res.json();
    return { data: json.posts, hasMore: json.hasMore };
  },
  { pageSize: 20 },
);

function PostFeed() {
  const {
    data,
    loading,
    error,
    hasMore,
    isFetchingMore,
    loadMore,
    observerTarget,
  } = useInfiniteAsyncChunk(postsChunk);

  if (loading && !data?.length) return <p>Loading...</p>;
  if (error) return <p>Error: {error.message}</p>;

  return (
    <div>
      <ul>
        {data?.map((p) => (
          <li key={p.id}>{p.title}</li>
        ))}
      </ul>
      <div ref={observerTarget} />
      {isFetchingMore && <p>Loading more...</p>}
      {!hasMore && <p>You've reached the end.</p>}
    </div>
  );
}

Options

OptionTypeDefaultDescription
autoLoadbooleantrueAuto-load next page when sentinel is visible
thresholdnumber1.0Intersection observer threshold
initialParamsobjectInitial params (excluding page and pageSize)

Deprecated in v2, removed in v3

The following hooks exist in v2 but will be removed in v3. Stop using them now and migrate to the patterns below.

useDerive → use useChunkValue

// ❌ v2 — deprecated
const doubled = useDerive(count, (n) => n * 2);

// ✅ v3 — derive outside, read with useChunkValue
const doubled = count.derive((n) => n * 2);
function Component() {
  const value = useChunkValue(doubled);
}

useComputed → use useChunkValue

// ❌ v2 — deprecated
const total = useComputed([price, qty], (p, q) => p * q);

// ✅ v3 — compute outside, read with useChunkValue
const total = computed([price, qty], (p, q) => p * q);
function Component() {
  const value = useChunkValue(total);
}

useChunkProperty → use useChunkValue with a selector

// ❌ v2 — deprecated
const name = useChunkProperty(user, "name");

// ✅ v3 — just use a selector
const name = useChunkValue(user, (u) => u.name);

useChunkValues → use useChunkValue per chunk

// ❌ v2 — deprecated, subtle pitfalls with inline arrays
const [price, qty] = useChunkValues([priceChunk, qtyChunk]);

// ✅ v3 — explicit and clear
const price = useChunkValue(priceChunk);
const qty = useChunkValue(qtyChunk);

All four deprecated hooks will be removed in v3 with no replacement other than the patterns shown above. Migrating now will make your v3 upgrade a non-event.


Summary

HookUse case
useChunkRead + write a chunk
useChunkValueRead-only, also for derived/computed/select
useAsyncChunkAsync state with loading/error/data
useInfiniteAsyncChunkInfinite scroll with auto-load

On this page