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.
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";56const 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 more19 </button>20 )}21 </div>22 );23};2425const Example = () => (26 <DataQueryProvider>27 <List />28 </DataQueryProvider>29);3031<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.
Examples
Open all 1 →1const fetchPage = async (cursor: string) => {2 const start = Number(cursor);34 return {5 items: [start, start + 1],6 nextCursor: start + 2 > 6 ? null : String(start + 2),7 };8};910const List = () => {11 const { items, hasMore, isLoadingMore, loadMore } = useCursorQuery({12 queryKey: ["example-list"],13 initialPage: { items: [1, 2], nextCursor: "3" },14 fetchPage,15 getItemKey: (item) => item,16 });1718 return (19 <div>20 <p>{items.join(", ")}</p>21 {hasMore && (22 <button type="button" disabled={isLoadingMore} onClick={loadMore}>23 Load more24 </button>25 )}26 </div>27 );28};2930const BasicUseDataQuery = () => (31 <DataQueryProvider>32 <List />33 </DataQueryProvider>34);3536render(<BasicUseDataQuery />);
Properties
DataQueryProvider props
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | The part of the app that reads through `useDataQuery` and `useCursorQuery`. |
staleMs | number | 5 * 60 * 1000 | How 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>| Parameter | Type | Default | Description |
|---|---|---|---|
__0 | UseDataQueryOptions<TData> | — | — |
Returns UseDataQueryResult<TData> — The data and its loading and error flags.
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) => voidReturns (keyPrefix: QueryKey) => void — `invalidate(keyPrefix)`, for example `invalidate(['orders'])`.
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>| Parameter | Type | Default | Description |
|---|---|---|---|
__0 | UseCursorQueryOptions<TItem> | — | — |
Returns UseCursorQueryResult<TItem> — The rows so far and the controls to load more.
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});