@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

ParameterTypeDefaultDescription
paramsVSQueryParamsdefaultParamsPage, limit, and any extra filters your API accepts

#Return values

FieldTypeDescription
listTBase[]Items for the current page. Always an array — never undefined.
paginationVSPaginationPage metadata: page, limit, totalPages, totalDocuments
isLoadingbooleantrue only on the very first fetch when there is no cached data. Use this to show a full-page skeleton.
isFetchingbooleantrue during any network request, including background refetches.
isRefetchingbooleantrue during a background refetch while data is already visible. Use for a subtle top progress bar.
isErrorbooleantrue when the most recent fetch threw an error.
errorError | nullThe error object. Check error.message.
refetchfunctionManually fire this query again. Wire to a "Try again" button.
invalidatefunctionMark the whole resource cache as stale. Every mounted useList and useGet for this resource refetches in the background.

Why three loading states? Using isFetching as a full-page skeleton guard will make your table disappear and reappear on every mutation (because mutations trigger cache invalidation → refetch → isFetching: true). Use isLoading for the initial skeleton, isRefetching for a subtle progress bar, and isFetching only 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}
      />
    </>
  );
}