Spoosh
Hooks

usePages

Bidirectional paginated data fetching with infinite scroll support

For usage patterns and examples, see the Infinite Queries guide.

Basic Usage

Given an API that returns:

{
  "items": [
    { "id": 1, "title": "Post 1" },
    { "id": 2, "title": "Post 2" }
  ],
  "meta": { "page": 1, "hasMore": true }
}
function PostList() {
  const { data, loading, canFetchNext, fetchNext, fetchingNext } =
    usePages(
      (api) => api("posts").GET({ query: { page: 1, limit: 20 } }),
      {
        canFetchNext: ({ lastPage }) => lastPage?.data?.meta.hasMore ?? false,
        nextPageRequest: ({ lastPage }) => ({
          query: { page: (lastPage?.data?.meta.page ?? 0) + 1 },
        }),
        merger: (pages) => pages.flatMap((p) => p.data?.items ?? []),
      }
    );

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      {data?.map((post) => <PostCard key={post.id} post={post} />)}

      {canFetchNext && (
        <button onClick={fetchNext} disabled={fetchingNext}>
          {fetchingNext ? "Loading..." : "Load More"}
        </button>
      )}
    </div>
  );
}

Options

OptionTypeRequiredDescription
merger(pages) => TItem[]YesMerge all pages into items
canFetchNext(ctx) => booleanNoCheck if next page exists. Default: () => false
nextPageRequest(ctx) => Partial<TRequest>NoBuild request for next page
canFetchPrev(ctx) => booleanNoCheck if previous page exists
prevPageRequest(ctx) => Partial<TRequest>NoBuild request for previous page
enabledbooleanNoWhether to fetch automatically

Context Object

// For canFetchNext and nextPageRequest
type NextContext<TData, TRequest> = {
  lastPage: InfinitePage<TData> | undefined;
  pages: InfinitePage<TData>[];
  request: TRequest;
};

// For canFetchPrev and prevPageRequest
type PrevContext<TData, TRequest> = {
  firstPage: InfinitePage<TData> | undefined;
  pages: InfinitePage<TData>[];
  request: TRequest;
};

// Each page in the pages array
type InfinitePage<TData> = {
  status: "pending" | "loading" | "success" | "error" | "stale";
  data?: TData;
  error?: TError;
  meta?: TMeta;
  input?: { query?; params?; body? };
};

Returns

PropertyTypeDescription
dataTItem[] | undefinedMerged items from all pages
pagesInfinitePage<TData>[]Array of all pages with status, data, and meta
loadingbooleanTrue during initial load
fetchingbooleanTrue during any fetch
fetchingNextbooleanTrue while fetching next page
fetchingPrevbooleanTrue while fetching previous
canFetchNextbooleanWhether next page exists
canFetchPrevbooleanWhether previous page exists
fetchNext() => Promise<void>Fetch the next page
fetchPrev() => Promise<void>Fetch the previous page
trigger(options?) => Promise<void>Trigger fetch with optional new request options
abort() => voidAbort current request
errorTError | undefinedError if request failed

On this page