@void-snippets/react
useList
List query hook with pagination, loading states, refetch, and invalidate.
#What it does
Fetches a paginated list for the resource. Separates three distinct "loading" states so you can show the right UI for each scenario.
#Signature
typescript
contactHooks.useList(params?: VSQueryParams): VSUseListReturn<Contact.Base>#Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
params | VSQueryParams | defaultParams | Page, limit, and any extra filters your API accepts |
#Return values
| Field | Type | Description |
|---|---|---|
list | TBase[] | Items for the current page. Always an array — never undefined. |
pagination | VSPagination | Page metadata: page, limit, totalPages, totalDocuments |
isLoading | boolean | true only on the very first fetch when there is no cached data. Use this to show a full-page skeleton. |
isFetching | boolean | true during any network request, including background refetches. |
isRefetching | boolean | true during a background refetch while data is already visible. Use for a subtle top progress bar. |
isError | boolean | true when the most recent fetch threw an error. |
error | Error | null | The error object. Check error.message. |
refetch | function | Manually fire this query again. Wire to a "Try again" button. |
invalidate | function | Mark the whole resource cache as stale. Every mounted useList and useGet for this resource refetches in the background. |
Why three loading states? Using
isFetchingas a full-page skeleton guard will make your table disappear and reappear on every mutation (because mutations trigger cache invalidation → refetch →isFetching: true). UseisLoadingfor the initial skeleton,isRefetchingfor a subtle progress bar, andisFetchingonly when you want to track any network activity.
#Example
tsx
function ContactsPage() {
const { queryParams, onPaginationChange } = usePagination(1, 20);
const [search, setSearch] = useState('');
const {
list, pagination, isLoading, isRefetching, isError, error, refetch,
} = contactHooks.useList({ ...queryParams, q: search });
if (isLoading) return <TableSkeleton />;
if (isError) return (
<ErrorState
message={error?.message}
action={<Button onClick={refetch}>Try again</Button>}
/>
);
return (
<>
{isRefetching && <LinearProgress />}
<SearchInput value={search} onChange={setSearch} />
{list.map(contact => <ContactRow key={contact._id} contact={contact} />)}
<Pagination
current={pagination.page}
pageSize={pagination.limit}
total={pagination.totalDocuments}
onChange={onPaginationChange}
/>
</>
);
}