The one wrapper over TanStack Query for the apps in this repo: `useDataQuery` reads a value and `useCursorQuery` loads a keyset-paged list one page at a time, so no component imports `@tanstack/react-query` itself.

pnpm add @behivetech/hooks.use-data-query

You are viewing Use Data Query v0.1.1, snapshotted 2026-11-10. The latest is v0.1.2. Go to the latest →

The one wrapper over TanStack Query for the apps in this repo: useDataQuery reads a value and useCursorQuery loads a keyset-paged list one page at a time, so no component imports @tanstack/react-query itself.

Usage

1import {
2 DataQueryProvider,
3 useCursorQuery,
4} from "@behivetech/hooks.use-data-query";
5
6const List = () => {
7 const { items, hasMore, loadMore } = useCursorQuery({
8 queryKey: ["example"],
9 initialPage: { items: [1, 2], nextCursor: "2" },
10 fetchPage: async () => ({ items: [3, 4], nextCursor: null }),
11 getItemKey: (item) => item,
12 });
13 return (
14 <div>
15 <p>{items.join(", ")}</p>
16 {hasMore && (
17 <button type="button" onClick={loadMore}>
18 Load more
19 </button>
20 )}
21 </div>
22 );
23};
24
25const Example = () => (
26 <DataQueryProvider>
27 <List />
28 </DataQueryProvider>
29);
30
31<Example />;

Mount one DataQueryProvider above the hooks. After a change the cached pages no longer show, useInvalidateData() drops them by key prefix. The cache lives in memory only: never persist it, and never put personal data or a secret in a query key.

The ```jsx block above runs live on the docs site. The signature, parameters and return value shown there come from the hook's TypeScript and JSDoc; keep the @param, @returns and @example tags current.

Properties

DataQueryProvider props

PropTypeDefaultDescription
childrenReactNode—The part of the app that reads through `useDataQuery` and `useCursorQuery`.
staleMsnumber5 * 60 * 1000How long a read counts as fresh, in milliseconds. Defaults to five minutes.

Hooks

useDataQuery hook

Reads one value and caches it in memory for the page's lifetime. It is the only door to `useQuery` for the apps in this repo, so cache settings live in one place and no component imports `@tanstack/react-query` itself.

useDataQuery(__0: UseDataQueryOptions<TData>): UseDataQueryResult<TData>
ParameterTypeDefaultDescription
__0UseDataQueryOptions<TData>——

Returns UseDataQueryResult<TData> — The data and its loading and error flags.

Example
1const { data, isLoading } = useDataQuery({
2 queryKey: ['refund', orderNumber],
3 queryFn: () => readRefund(orderNumber),
4});

useInvalidateData hook

Returns a function that drops what the app cached under a key prefix. Reads no component is showing are removed, so the next visit starts from the server's fresh first page. Reads on screen stay (a mounted list keeps the rows it shows) and are marked stale, and `useDataQuery` reads among them are fetched again. Call it after a change the cached data no longer shows, such as an order action, and leave it alone when nothing changed, so coming back keeps the pages already loaded.

useInvalidateData(): (keyPrefix: QueryKey) => void

Returns (keyPrefix: QueryKey) => void — `invalidate(keyPrefix)`, for example `invalidate(['orders'])`.

Example
1const invalidate = useInvalidateData();
2await refundOrder(number);
3invalidate(['orders']);

useCursorQuery hook

Loads a keyset-paged list one page at a time over `useInfiniteQuery`. The first page comes from the caller (the server render) and only the pages after it are cached, so coming back to the list shows the fresh first page and the pages the reader had loaded. A failed read keeps the rows and waits for `retry`; nothing retries by itself.

useCursorQuery(__0: UseCursorQueryOptions<TItem>): UseCursorQueryResult<TItem>
ParameterTypeDefaultDescription
__0UseCursorQueryOptions<TItem>——

Returns UseCursorQueryResult<TItem> — The rows so far and the controls to load more.

Example
1const { items, hasMore, loadMore } = useCursorQuery({
2 queryKey: ['orders', status, firstPage.nextCursor],
3 initialPage: firstPage,
4 fetchPage: (cursor) => loadMoreOrders({ status, cursor }),
5 getItemKey: (order) => order.number,
6});